Don't use timestamps as parameter defaults

Constructs like

    def calculate_sunrise_sunset(locator, calc_date=datetime.utcnow()):

are actually wrong because they initialize the parameter default at
module load time, not at function call time. If the program is running
over some time, the result will be wrong.

As a side-effect, this fix makes the docs (and the whole project) build
reproducibly because previously the build time was embedded in the
sphinx docs:

    lookup_prefix(prefix, timestamp=datetime.datetime(2019, 11, 27, 3, 4, 36, 93157, tzinfo=<UTC>))
This commit is contained in:
Christoph Berg 2019-12-15 22:21:46 +01:00
parent c8fb844e2d
commit 6fdb61e531
3 changed files with 60 additions and 19 deletions

View file

@ -10,7 +10,6 @@ from pyhamtools.consts import LookupConventions as const
from pyhamtools.callsign_exceptions import callsign_exceptions
UTC = pytz.UTC
timestamp_now = datetime.utcnow().replace(tzinfo=UTC)
if sys.version_info < (2, 7, ):
class NullHandler(logging.Handler):
@ -78,9 +77,11 @@ class Callinfo(object):
else:
raise ValueError
def _iterate_prefix(self, callsign, timestamp=timestamp_now):
def _iterate_prefix(self, callsign, timestamp=None):
"""truncate call until it corresponds to a Prefix in the database"""
prefix = callsign
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=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):
@ -109,7 +110,7 @@ class Callinfo(object):
check = callsign[-4:].upper()
return "/B" in check or "/BCN" in check
def _dismantle_callsign(self, callsign, timestamp=timestamp_now):
def _dismantle_callsign(self, callsign, timestamp=None):
""" try to identify the callsign's identity by analyzing it in the following order:
Args:
@ -122,6 +123,8 @@ class Callinfo(object):
"""
entire_callsign = callsign.upper()
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if re.search('[/A-Z0-9\-]{3,15}', entire_callsign): # make sure the call has at least 3 characters
@ -221,7 +224,9 @@ class Callinfo(object):
self._logger.debug("Could not decode " + callsign)
raise KeyError("Callsign could not be decoded")
def _lookup_callsign(self, callsign, timestamp=timestamp_now):
def _lookup_callsign(self, callsign, timestamp=None):
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
# Check if operation is invalid
invalid = False
@ -264,7 +269,7 @@ class Callinfo(object):
# Dismantel the callsign and check if the prefix is known
return self._dismantle_callsign(callsign, timestamp)
def get_all(self, callsign, timestamp=timestamp_now):
def get_all(self, callsign, timestamp=None):
""" Lookup a callsign and return all data available from the underlying database
Args:
@ -302,6 +307,9 @@ class Callinfo(object):
would be missing with Clublog (API or XML) :py:class:`LookupLib`.
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
callsign_data = self._lookup_callsign(callsign, timestamp)
try:
@ -312,7 +320,7 @@ class Callinfo(object):
return callsign_data
def is_valid_callsign(self, callsign, timestamp=timestamp_now):
def is_valid_callsign(self, callsign, timestamp=None):
""" Checks if a callsign is valid
Args:
@ -332,13 +340,16 @@ class Callinfo(object):
True
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
try:
if self.get_all(callsign, timestamp):
return True
except KeyError:
return False
def get_lat_long(self, callsign, timestamp=timestamp_now):
def get_lat_long(self, callsign, timestamp=None):
""" Returns Latitude and Longitude for a callsign
Args:
@ -369,13 +380,16 @@ class Callinfo(object):
dedicated entry in the database exists. Best results will be retrieved with QRZ.com Lookup.
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
callsign_data = self.get_all(callsign, timestamp=timestamp)
return {
const.LATITUDE: callsign_data[const.LATITUDE],
const.LONGITUDE: callsign_data[const.LONGITUDE]
}
def get_cqz(self, callsign, timestamp=timestamp_now):
def get_cqz(self, callsign, timestamp=None):
""" Returns CQ Zone of a callsign
Args:
@ -389,9 +403,12 @@ class Callinfo(object):
KeyError: no CQ Zone found for callsign
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
return self.get_all(callsign, timestamp)[const.CQZ]
def get_ituz(self, callsign, timestamp=timestamp_now):
def get_ituz(self, callsign, timestamp=None):
""" Returns ITU Zone of a callsign
Args:
@ -408,9 +425,12 @@ class Callinfo(object):
Currently, only Country-files.com lookup database contains ITU Zones
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
return self.get_all(callsign, timestamp)[const.ITUZ]
def get_country_name(self, callsign, timestamp=timestamp_now):
def get_country_name(self, callsign, timestamp=None):
""" Returns the country name where the callsign is located
Args:
@ -432,9 +452,12 @@ class Callinfo(object):
- Clublog: "FEDERAL REPUBLIC OF GERMANY"
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
return self.get_all(callsign, timestamp)[const.COUNTRY]
def get_adif_id(self, callsign, timestamp=timestamp_now):
def get_adif_id(self, callsign, timestamp=None):
""" Returns ADIF id of a callsign's country
Args:
@ -448,9 +471,12 @@ class Callinfo(object):
KeyError: No Country found for callsign
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
return self.get_all(callsign, timestamp)[const.ADIF]
def get_continent(self, callsign, timestamp=timestamp_now):
def get_continent(self, callsign, timestamp=None):
""" Returns the continent Identifier of a callsign
Args:
@ -474,4 +500,7 @@ class Callinfo(object):
- OC: Oceania
- AN: Antarctica
"""
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
return self.get_all(callsign, timestamp)[const.CONTINENT]

View file

@ -265,7 +265,7 @@ def calculate_heading_longpath(locator1, locator2):
return lp
def calculate_sunrise_sunset(locator, calc_date=datetime.utcnow()):
def calculate_sunrise_sunset(locator, calc_date=None):
"""calculates the next sunset and sunrise for a Maidenhead locator at a give date & time
Args:
@ -303,6 +303,8 @@ def calculate_sunrise_sunset(locator, calc_date=datetime.utcnow()):
latitude, longitude = locator_to_latlong(locator)
if calc_date is None:
calc_date = datetime.utcnow()
if type(calc_date) != datetime:
raise ValueError

View file

@ -22,7 +22,6 @@ from .consts import LookupConventions as const
from .exceptions import APIKeyMissingError
UTC = pytz.UTC
timestamp_now = datetime.utcnow().replace(tzinfo=UTC)
if sys.version_info < (2, 7,):
class NullHandler(logging.Handler):
@ -319,7 +318,7 @@ class LookupLib(object):
return new_dict
def lookup_callsign(self, callsign=None, timestamp=timestamp_now):
def lookup_callsign(self, callsign=None, timestamp=None):
"""
Returns lookup data if an exception exists for a callsign
@ -364,6 +363,8 @@ class LookupLib(object):
"""
callsign = callsign.strip().upper()
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if self._lookuptype == "clublogapi":
callsign_data = self._lookup_clublogAPI(callsign=callsign, timestamp=timestamp, apikey=self._apikey)
@ -482,7 +483,7 @@ class LookupLib(object):
raise KeyError
def lookup_prefix(self, prefix, timestamp=timestamp_now):
def lookup_prefix(self, prefix, timestamp=None):
"""
Returns lookup data of a Prefix
@ -524,6 +525,8 @@ class LookupLib(object):
"""
prefix = prefix.strip().upper()
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if self._lookuptype == "clublogxml" or self._lookuptype == "countryfile":
@ -537,7 +540,7 @@ class LookupLib(object):
# no matching case
raise KeyError
def is_invalid_operation(self, callsign, timestamp=datetime.utcnow().replace(tzinfo=UTC)):
def is_invalid_operation(self, callsign, timestamp=None):
"""
Returns True if an operations is known as invalid
@ -577,6 +580,8 @@ class LookupLib(object):
"""
callsign = callsign.strip().upper()
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if self._lookuptype == "clublogxml":
@ -624,7 +629,7 @@ class LookupLib(object):
raise KeyError
def lookup_zone_exception(self, callsign, timestamp=datetime.utcnow().replace(tzinfo=UTC)):
def lookup_zone_exception(self, callsign, timestamp=None):
"""
Returns a CQ Zone if an exception exists for the given callsign
@ -659,6 +664,8 @@ class LookupLib(object):
"""
callsign = callsign.strip().upper()
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if self._lookuptype == "clublogxml":
@ -672,7 +679,7 @@ class LookupLib(object):
#no matching case
raise KeyError
def _lookup_clublogAPI(self, callsign=None, timestamp=timestamp_now, url="https://secure.clublog.org/dxcc", apikey=None):
def _lookup_clublogAPI(self, callsign=None, timestamp=None, url="https://secure.clublog.org/dxcc", apikey=None):
""" Set up the Lookup object for Clublog Online API
"""
@ -686,6 +693,9 @@ class LookupLib(object):
"call" : callsign
}
if timestamp is None:
timestamp = datetime.utcnow().replace(tzinfo=UTC)
if sys.version_info.major == 3:
encodeurl = url + "?" + urllib.parse.urlencode(params)
else: