Skip to content
Closed
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
6 changes: 3 additions & 3 deletions src/euring/codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
load_species_map,
)
from .exceptions import EuringConstraintException, EuringLookupException
from .utils import euring_dms_to_float
from .utils import euring_dms_to_float, is_all_hyphens

LOOKUP_EURING_CODE_IDENTIFIER = load_code_map("euring_code_identifier")
LOOKUP_CONDITION = load_code_map("condition")
Expand Down Expand Up @@ -211,7 +211,7 @@ def parse_direction(value: str) -> int | None:
if value is None:
raise EuringConstraintException(f'Value "{value}" is not a valid direction.')
value_str = f"{value}"
if value_str and set(value_str) == {"-"}:
if is_all_hyphens(value_str):
return None
if value_str.startswith("-"):
raise EuringConstraintException(f'Value "{value}" is not a valid direction.')
Expand Down Expand Up @@ -315,7 +315,7 @@ def lookup_date(value: str | int) -> date:

def parse_date(value: str) -> str:
"""Validate that date placeholders are not used, then return the raw value."""
if value and set(value) == {"-"}:
if is_all_hyphens(value):
raise EuringConstraintException("Date cannot be all dashes; provide an estimated real date instead.")
return value

Expand Down
8 changes: 4 additions & 4 deletions src/euring/field_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datetime import date as dt_date
from typing import Any

from euring.utils import euring_lat_to_dms, euring_lng_to_dms, is_empty
from euring.utils import euring_lat_to_dms, euring_lng_to_dms, is_all_hyphens, is_empty

from .codes import lookup_description
from .exceptions import EuringConstraintException, EuringTypeException
Expand Down Expand Up @@ -93,7 +93,7 @@ def _validate_raw(self, raw: str) -> str | None:

def _coerce_type(self, raw: str) -> Any:
if self.euring_type == TYPE_INTEGER:
if set(raw) == {"-"}:
if is_all_hyphens(raw):
return None
return int(raw)
if self.euring_type == TYPE_NUMERIC:
Expand All @@ -111,7 +111,7 @@ def _coerce_value_type(self, raw: str) -> Any:
if self.value_type == "code_str":
return raw
if self.value_type == "int":
if set(raw) == {"-"}:
if is_all_hyphens(raw):
return None
return int(raw)
if self.value_type == "float":
Expand Down Expand Up @@ -187,7 +187,7 @@ def encode_for_format(self, value: Any | None, *, format: str) -> str:
raise EuringTypeException(f'Value "{str_value}" is not valid for type {self.euring_type}.')
return str_value

if self.euring_type == TYPE_INTEGER and isinstance(value, str) and value and set(value) == {"-"}:
if self.euring_type == TYPE_INTEGER and isinstance(value, str) and is_all_hyphens(value):
return self.encode_for_format(None, format=format)

str_value = f"{value}"
Expand Down
4 changes: 2 additions & 2 deletions src/euring/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
unknown_format_error,
)
from .rules import record_rule_errors, requires_euring2020
from .utils import euring_lat_to_dms, euring_lng_to_dms, is_empty
from .utils import euring_lat_to_dms, euring_lng_to_dms, is_all_hyphens, is_empty


class EuringRecord:
Expand Down Expand Up @@ -148,7 +148,7 @@ def _validate_fields(self) -> list[dict[str, object]]:
field_obj = replace(field_obj, variable_length=False)
encoded_value = _serialize_field_value(field, value, self.format)
raw_value = encoded_value
if key == "date" and had_empty_value and raw_value and set(raw_value) == {"-"}:
if key == "date" and had_empty_value and is_all_hyphens(raw_value):
# Treat placeholder dashes for missing required dates as empty so
# non-strict mode only reports a missing-required-field error.
raw_value = ""
Expand Down
5 changes: 5 additions & 0 deletions src/euring/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def is_empty(value: Any) -> bool:
return value in (None, "")


def is_all_hyphens(value: str) -> bool:
"""Return True when a non-empty string consists of only hyphens."""
return bool(value) and set(value) == {"-"}


def euring_dms_to_float(value: str) -> float:
"""Convert EURING DMS coordinate text into decimal degrees."""
try:
Expand Down
88 changes: 52 additions & 36 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,57 @@
euring_scheme_export_format,
euring_species_export_format,
)
from euring.utils import is_all_hyphens, is_empty


class TestUtils:
def test_dms_conversion(self):
# Test DMS to float
lat_decimal = euring_dms_to_float("+420500")
lng_decimal = euring_dms_to_float("-0100203")
assert abs(lat_decimal - 42.083333) < 1e-5
assert abs(lng_decimal - (-10.034167)) < 1e-5

# Test float to DMS (round trip)
assert euring_lat_to_dms(lat_decimal) == "+420500"
assert euring_lng_to_dms(lng_decimal) == "-0100203"
dms = euring_float_to_dms(12.25)
assert dms["quadrant"] == "+"
assert dms["degrees"] == 12
assert dms["minutes"] == 15
assert dms["seconds"] == 0.0

def test_dms_conversion_invalid(self):
with pytest.raises(EuringConstraintException):
euring_dms_to_float("bogus")

def test_identification_format(self):
assert euring_identification_display_format("ab.12-3") == "AB123"
assert euring_identification_export_format("AB123") == "AB.....123"

def test_scheme_format(self):
assert euring_scheme_export_format("GB") == " GB"
assert euring_scheme_export_format("ABCDE") == "ABC"

def test_species_format(self):
assert euring_species_export_format("123") == "00123"
assert euring_species_export_format("12345") == "12345"
with pytest.raises(ValueError):
euring_species_export_format("123456")
with pytest.raises(ValueError):
euring_species_export_format("not-a-number")
def test_dms_conversion():
# Test DMS to float
lat_decimal = euring_dms_to_float("+420500")
lng_decimal = euring_dms_to_float("-0100203")
assert abs(lat_decimal - 42.083333) < 1e-5
assert abs(lng_decimal - (-10.034167)) < 1e-5

# Test float to DMS (round trip)
assert euring_lat_to_dms(lat_decimal) == "+420500"
assert euring_lng_to_dms(lng_decimal) == "-0100203"
dms = euring_float_to_dms(12.25)
assert dms["quadrant"] == "+"
assert dms["degrees"] == 12
assert dms["minutes"] == 15
assert dms["seconds"] == 0.0


def test_dms_conversion_invalid():
with pytest.raises(EuringConstraintException):
euring_dms_to_float("bogus")


def test_identification_format():
assert euring_identification_display_format("ab.12-3") == "AB123"
assert euring_identification_export_format("AB123") == "AB.....123"


def test_scheme_format():
assert euring_scheme_export_format("GB") == " GB"
assert euring_scheme_export_format("ABCDE") == "ABC"


def test_species_format():
assert euring_species_export_format("123") == "00123"
assert euring_species_export_format("12345") == "12345"
with pytest.raises(ValueError):
euring_species_export_format("123456")
with pytest.raises(ValueError):
euring_species_export_format("not-a-number")


def test_is_empty():
assert is_empty("")
assert is_empty(None)
assert not is_empty(0)
assert not is_empty(False)


def test_is_all_hyphens():
assert is_all_hyphens("-")
assert not is_all_hyphens("")