Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions pyhamtools/callinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,24 @@ def get_homecall(callsign):
"""

callsign = callsign.upper()
homecall = re.search('[\\d]{0,1}[A-Z]{1,2}\\d([A-Z]{1,4}|\\d{3,3}|\\d{1,3}[A-Z])[A-Z]{0,5}', callsign)

# Prefer splitting on '/', as portable calls often appear as chains like DL/SQ5FOX/M/DL.
# Pick the first token that looks like a real callsign and let higher-level logic validate it.
token_candidates = [token.strip() for token in callsign.split('/') if token.strip()]
token_candidates = [re.sub(r'-\d{1,3}$', '', token) for token in token_candidates]

# Accept typical callsigns and special patterns with digits in the suffix (e.g. 3Z3Z3Z).
token_pattern = re.compile(r'^[\d]{0,1}[A-Z]{1,2}\d{1,4}[A-Z0-9]{1,8}$')
for token in token_candidates:
if token_pattern.match(token):
return token

# Fallback: search inside the string for a callsign-like pattern.
homecall = re.search(r'[\d]{0,1}[A-Z]{1,2}\d{1,4}[A-Z0-9]{1,8}', callsign)
if homecall:
homecall = homecall.group(0)
return homecall
else:
raise ValueError
return homecall.group(0)

raise ValueError

def _iterate_prefix(self, callsign, timestamp=None):
"""truncate call until it corresponds to a Prefix in the database"""
Expand Down Expand Up @@ -195,6 +207,11 @@ def _dismantle_callsign(self, callsign, timestamp=None):
elif re.match('^[\\d]{0,1}[A-Z]{1,2}\\d{1,4}([A-Z]{1,4}|[A-Z]{1,2}\\d{0,3})[A-Z]{0,5}$', callsign):
return self._iterate_prefix(callsign, timestamp)

# Some special callsigns contain digits in the suffix (e.g. 3Z3Z3Z).
# Fall back to a more permissive pattern and let the prefix database decide.
elif re.match('^[\\d]{0,1}[A-Z]{1,2}\\d{1,4}[A-Z0-9]{1,8}$', callsign):
return self._iterate_prefix(callsign, timestamp)

# callsigns with prefixes (xxx/callsign)
elif re.search('^[A-Z0-9]{1,4}/', entire_callsign):
pfx = re.search('^[A-Z0-9]{1,4}/', entire_callsign)
Expand Down
8 changes: 8 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,23 @@ def fixApiKey(request):
@pytest.fixture(scope="module", params=["clublogapi", "clublogxml", "countryfile"])
def fixGeneralApi(request, fixApiKey):
"""Fixture returning all possible instances of LookupLib"""
if request.param in ("clublogapi", "clublogxml") and not fixApiKey:
pytest.skip("CLUBLOG_APIKEY not set; skipping Clublog-backed tests")
Lib = LookupLib(request.param, fixApiKey)
# pytest.skip("better later")
return(Lib)

@pytest.fixture(scope="module")
def fixClublogApi(request, fixApiKey):
if not fixApiKey:
pytest.skip("CLUBLOG_APIKEY not set; skipping clublogapi tests")
Lib = LookupLib("clublogapi", fixApiKey)
return(Lib)

@pytest.fixture(scope="module")
def fixClublogXML(request, fixApiKey):
if not fixApiKey:
pytest.skip("CLUBLOG_APIKEY not set; skipping clublogxml tests")
Lib = LookupLib("clublogxml", fixApiKey)
return(Lib)

Expand All @@ -78,6 +84,8 @@ def fixCountryFile(request):

@pytest.fixture(scope="module", params=["clublogxml", "countryfile"])
def fix_callinfo(request, fixApiKey):
if request.param == "clublogxml" and not fixApiKey:
pytest.skip("CLUBLOG_APIKEY not set; skipping clublogxml-based callinfo tests")
lib = LookupLib(request.param, fixApiKey)
callinfo = Callinfo(lib)
return(callinfo)
Expand Down
19 changes: 17 additions & 2 deletions test/test_callinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,22 @@ def test_if_beacon(self, fix_callinfo):
assert not fix_callinfo.check_if_beacon("DH1TW")

def test_get_homecall(self, fix_callinfo):
assert fix_callinfo.get_homecall("HB9/DH1TW") == "DH1TW"
assert fix_callinfo.get_homecall("SM3/DH1TW/P") == "DH1TW"
cases = [
("HB9/DH1TW", "DH1TW"),
("SM3/DH1TW/P", "DH1TW"),
("SP5ABC", "SP5ABC"),
("SP/SP5ABC", "SP5ABC"),
("SP5ABC/W5", "SP5ABC"),
("DL/SQ5FOX/M/DL", "SQ5FOX"),
("3z3z3z", "3Z3Z3Z"),
("DL/3z3z3z", "3Z3Z3Z"),
("DL/3z3z3z/am/m/ok", "3Z3Z3Z"),
("N0CALL/P", "N0CALL"),
("W5/N0CALL", "N0CALL"),
]

for input_call, expected in cases:
assert fix_callinfo.get_homecall(input_call) == expected
with pytest.raises(ValueError):
fix_callinfo.get_homecall("QRM")

Expand Down Expand Up @@ -395,6 +409,7 @@ def test_get_all(self, fix_callinfo):

def test_is_valid_callsign(self, fix_callinfo):
assert fix_callinfo.is_valid_callsign("DH1TW")
assert fix_callinfo.is_valid_callsign("3Z3Z3Z")
assert not fix_callinfo.is_valid_callsign("QRM")

def test_get_lat_long(self, fix_callinfo):
Expand Down