Skip to content

Release 1.15.0

Choose a tag to compare

@AngusHsu AngusHsu released this 08 Oct 01:38
11088b0

Added

  • Add generated country metadata, failure-reason and input-format guides, executed and strictly typed documentation
    examples, and contributor/country workflow templates
    (#328).

  • Add primary-only normalize_id(), format_id() and get_input_mask() with frozen InputMask results.
    Metadata-driven formatting checks length, not validity; input patterns use ASCII uppercase letters/digits.
    Swedish formatting preserves a significant + in both 10-digit and 12-digit forms; preferred mask patterns
    remain literal minus layouts. Greece's primary metadata example now uses already-accepted Latin letters;
    Greek validation and formatting remain unchanged (#321).

  • Add failure_reason(id_class, id_number) for primary and secondary ID types. Unified validation and parsing now
    report best-effort length, format, checksum and embedded-calendar-date reasons; inconclusive checks and exceptions
    retain VALIDATION_FAILED. Date tracing is isolated between nested calls, threads and async tasks, and country
    validity, parser and checksum behavior is unchanged (#322).

  • Add idnumbers.parse_id_info(country, id_number) for a country's primary NationalID, returning the frozen
    ParseSuccess / ParseFailure union ParseIdInfoResult. The ok discriminator supports strict type narrowing;
    success includes a fresh dictionary of the country parser's information. Lookup and validation failures match
    validate(), while valid IDs without extractable information give NOT_PARSABLE. Country parsers are unchanged
    (#319).

  • Add a country registry, idnumbers.registry, which is re-exported from idnumbers. get_validator('tw'),
    get_country('TWN') and resolve_country('tw') find the NationalID class, a CountryEntry (alpha-3 and alpha-2
    code, name, NationalID and all ID types) or the alpha-3 code of a country by its alpha-2 or alpha-3 code in any
    letter case, and give None for anything unknown instead of raising. list_supported_countries() lists the 78
    countries, and register() adds a custom country. The names are the English names of Unicode CLDR 48.2.3. AUS and
    GRC keep their current NationalID, and a country module is imported on the first lookup of its country
    (#318).

  • Add idnumbers.validate(country, id_number), which validates an ID number of any supported country and returns a
    frozen ValidationResult with is_valid, the alpha-3 country_code, the id_number, the extracted_info of a
    valid ID number (the parse() result, None when the ID type isn't parsable), a reason and an error_message. It
    never raises, and a result is truthy when it is valid. An unknown country gives the reason unsupported_country,
    other failures a best-effort diagnostic reason. validate_many() validates (country, id_number) pairs, and
    FailureReason lists the reasons, which later releases may extend. The country classes are unchanged
    (#317).

  • Describe every ID class with seven more METADATA keys: country_name, id_type, official_name (None for an
    ID with no local-language name), display_format, example (a synthetic number the class validates),
    checksum_algorithm (None without a check digit) and masks, the layouts the class accepts. METADATA is now an
    IdMetadata, a typed SimpleNamespace subclass in idnumbers.nationalid.metadata, so attribute access, vars(),
    copy() and getattr() work as before. A class made by alias_of, such as AUS.NationalID, now has the name,
    module and docstring of its target instead of AliasType, which changes its repr. CAN.SocialInsuranceNumber
    gets the deprecated key the other classes already had, and tools.scan_ids lists the classes through the country
    registry (#320).

Changed

  • Ship the PEP 561 py.typed marker, so type checkers use the package's annotations, and correct the annotations
    that disagreed with the runtime values: the CHECK_DIGIT alias, the untyped normalize and checksum helpers,
    the COL and KAZ checksum methods that return False for malformed input, the AUS TFN and KWT civil-number
    checksum methods, which can return 10, and the LTU extract_year_base_gender result, which is never None
    (#325).

  • Move the build metadata from setup.py to a PEP 621 pyproject.toml. The license is now the SPDX expression MIT
    instead of the deprecated license classifier (#465), and
    building from source needs setuptools 77 or later, which pip and uv install automatically
    (#325).

  • CI type-checks the package with strict mypy for Python 3.9 and checks the built distributions with
    twine check --strict (#325).

  • Re-export the ID classes explicitly (import X as X) from the country modules such as AUS, so strict type
    checkers (mypy --strict, which disallows implicit re-exports) accept
    from idnumbers.nationalid.AUS import MedicareNumber and AUS.MedicareNumber; nothing changes at runtime
    (#326).

  • Prepare isolated, guarded PyPI publication and opt-in Trusted Publishing with attestations; retain the working
    default token route and existing secrets. External publisher setup and the first verified trusted release remain
    pending maintainer action. Document release ordering and 1.14.0-to-1.15.0 migration
    (#327).

  • Add dev-only Hypothesis checks of arbitrary Unicode inputs for every registered ID type, metadata method
    contracts, out-of-range lengths and checksum mutations, with explicit synthetic witnesses for checksum limits
    (#324).

  • CI lints with ruff, enforces a 97% line-and-branch coverage floor, runs the README Python examples and
    informational benchmarks, keeps GitHub Actions updated with Dependabot, and reports a single ci-summary result
    (#326). Formatting with ruff format and a coverage badge
    await a decision in #485.

  • Vendor the Node port's parity corpus (78 countries, 17,960 vectors) at a pinned port commit and record Python's own
    validity for it, so a validity change on any shared vector fails a test and gets a port note;
    scripts/sync_parity_corpus.py refreshes both (#323).