Skip to content
Merged
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
9 changes: 5 additions & 4 deletions .github/workflows/on-push-verify-build.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see:
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

name: Build and Test

on:
Expand All @@ -22,21 +18,26 @@ jobs:
with:
# Disabling shallow clones is recommended for improving the relevancy of sonar reporting
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel build
pip install -r requirements.txt
pip install -r requirements-test.txt

- name: Build package
run: python -m build

- name: Run tests with pytest
run: |
pip install pytest pytest-cov
pytest --cov=./ --cov-report=xml --doctest-modules

- name: SonarQube Scan
uses: SonarSource/[email protected] # Ex: v4.1.0, See the latest version at https://github.com/marketplace/actions/official-sonarqube-scan
env:
Expand Down
3 changes: 2 additions & 1 deletion kat_bulgaria/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from .helpers import strtobool


class PersonalDocumentType:
class PersonalIdentificationType:
"""Personal Document Type."""

DRIVING_LICENSE = "driving_license"
NATIONAL_ID = "national_id"
CAR_PLATE_NUM = "car_plate_num"


@dataclass
Expand Down
1 change: 1 addition & 0 deletions kat_bulgaria/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class KatErrorSubtype(Enum):
VALIDATION_EGN_INVALID = "invalid_egn"
VALIDATION_GOV_ID_NUMBER_INVALID = "invalid_gov_id_number"
VALIDATION_DRIVING_LICENSE_INVALID = "invalid_driving_license"
VALIDATION_CAR_PLATE_NUMBER_INVALID = "invalid_car_plate_number"
VALIDATION_BULSTAT_INVALID = "invalid_bulstat"
VALIDATION_USER_NOT_FOUND_ONLINE = "user_not_found_online"

Expand Down
44 changes: 29 additions & 15 deletions kat_bulgaria/kat_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
from httpx import AsyncClient

from .errors import KatError, KatErrorType, KatErrorSubtype
from .data_models import KatObligationApiResponse, KatObligation, PersonalDocumentType
from .data_models import KatObligationApiResponse, KatObligation, PersonalIdentificationType

_REQUEST_TIMEOUT = 10

# Знам че това е грозно, но е много по-лесно и безпроблемно от custom URL builder за 4 url-a.
_URL_PERSON_DRIVING_LICENSE = "https://e-uslugi.mvr.bg/api/Obligations/AND?obligatedPersonType=1&additinalDataForObligatedPersonType=1&mode=1&obligedPersonIdent={egn}&drivingLicenceNumber={identifier}"
_URL_PERSON_GOV_ID = "https://e-uslugi.mvr.bg/api/Obligations/AND?obligatedPersonType=1&additinalDataForObligatedPersonType=2&mode=1&obligedPersonIdent={egn}&personalDocumentNumber={identifier}"
_URL_PERSON_CAR_PLATE = "https://e-uslugi.mvr.bg/api/Obligations/AND?obligatedPersonType=1&additinalDataForObligatedPersonType=3&mode=1&obligedPersonIdent={egn}&foreignVehicleNumber={car_plate_num}"
_URL_BUSINESS = "https://e-uslugi.mvr.bg/api/Obligations/AND?obligatedPersonType=2&additinalDataForObligatedPersonType=1&mode=1&obligedPersonIdent={egn}&personalDocumentNumber={identifier}&uic={bulstat}"

ERR_INVALID_EGN = "EGN is not valid."
ERR_INVALID_LICENSE = "Driving License Number is not valid."
ERR_INVALID_GOV_ID = "Government ID Number is not valid."
ERR_INVALID_CAR_PLATE_NUM = "Car plate number is not valid."
ERR_INVALID_BULSTAT = "BULSTAT is not valid."

ERR_INVALID_USER_DATA = "User data (EGN and Identity Document combination) is not valid."
Expand All @@ -34,6 +37,7 @@
# ID Format Supports "123456789" and "AA1234567"
REGEX_GOVT_ID = r"^[0-9]{9}|[A-Z]{2}[0-9]{7}$"
REGEX_BULSTAT = r"^[0-9]{9}$"
REGEX_CAR_PLATE = r"^[A-Z0-9]+$"


class KatApiClient:
Expand All @@ -57,7 +61,7 @@ def __validate_response(self, data: KatObligationApiResponse):
async def __get_obligations_from_url(
self,
url: str,
identifier_type: PersonalDocumentType,
identifier_type: PersonalIdentificationType,
identifier: str,
external_httpx_client: AsyncClient | None = None
) -> list[KatObligation]:
Expand Down Expand Up @@ -127,8 +131,8 @@ async def __get_obligations_from_url(
def __validate_credentials_individual(
self,
egn: str,
document_type: str,
document_number: str):
identifier_type: str,
identifier: str):
"""Validates the combination of EGN and License number for an individual."""

# Validate EGN
Expand All @@ -139,23 +143,29 @@ def __validate_credentials_individual(
ERR_INVALID_EGN)

# Validate Driving License Number
if document_type == PersonalDocumentType.NATIONAL_ID:
if document_number is None or re.search(REGEX_GOVT_ID, document_number) is None:
if identifier_type == PersonalIdentificationType.NATIONAL_ID:
if identifier is None or re.search(REGEX_GOVT_ID, identifier) is None:
raise KatError(
KatErrorType.VALIDATION_ERROR, KatErrorSubtype.VALIDATION_GOV_ID_NUMBER_INVALID, ERR_INVALID_GOV_ID)

# Validate Driving License Number
if document_type == PersonalDocumentType.DRIVING_LICENSE:
if document_number is None or re.search(REGEX_DRIVING_LICENSE, document_number) is None:
if identifier_type == PersonalIdentificationType.DRIVING_LICENSE:
if identifier is None or re.search(REGEX_DRIVING_LICENSE, identifier) is None:
raise KatError(
KatErrorType.VALIDATION_ERROR, KatErrorSubtype.VALIDATION_DRIVING_LICENSE_INVALID, ERR_INVALID_LICENSE)

# Validate Car Plate Number
if identifier_type == PersonalIdentificationType.CAR_PLATE_NUM:
if identifier is None or re.search(REGEX_CAR_PLATE, identifier) is None:
raise KatError(
KatErrorType.VALIDATION_ERROR, KatErrorSubtype.VALIDATION_CAR_PLATE_NUMBER_INVALID, ERR_INVALID_CAR_PLATE_NUM)

return True

def __validate_credentials_business(
self,
egn: str,
identifier: str,
govt_id_number: str,
bulstat: str) -> bool:
"""Validates the combination of EGN, Government ID Number and BULSTAT for a business."""

Expand All @@ -165,7 +175,7 @@ def __validate_credentials_business(
ERR_INVALID_EGN)

# Validate Government ID Number
if identifier is None or re.search(REGEX_GOVT_ID, identifier) is None:
if govt_id_number is None or re.search(REGEX_GOVT_ID, govt_id_number) is None:
raise KatError(
KatErrorType.VALIDATION_ERROR, KatErrorSubtype.VALIDATION_GOV_ID_NUMBER_INVALID, ERR_INVALID_GOV_ID)

Expand All @@ -187,8 +197,8 @@ async def get_obligations_individual(
Gets a list of obligations/fines for an individual

:param egn: EGN (National Identification Number)
:param identifier_type: PersonalDocumentType.NATIONAL_ID or PersonalDocumentType.DRIVING_LICENSE
:param identifier: Number of identification card (National ID or Driving License)
:param identifier_type: PersonalIdentificationType.NATIONAL_ID, PersonalIdentificationType.DRIVING_LICENSE or PersonalIdentificationType.CAR_PLATE_NUM
:param identifier: Number of identification card (National ID or Driving License) or Car Plate Number
:param external_httpx_client: Externally created httpx client (optional)
"""

Expand All @@ -197,14 +207,18 @@ async def get_obligations_individual(

url: str

if identifier_type == PersonalDocumentType.NATIONAL_ID:
if identifier_type == PersonalIdentificationType.NATIONAL_ID:
url = _URL_PERSON_GOV_ID.format(
egn=egn, identifier=identifier)

if identifier_type == PersonalDocumentType.DRIVING_LICENSE:
if identifier_type == PersonalIdentificationType.DRIVING_LICENSE:
url = _URL_PERSON_DRIVING_LICENSE.format(
egn=egn, identifier=identifier)

if identifier_type == PersonalIdentificationType.CAR_PLATE_NUM:
url = _URL_PERSON_CAR_PLATE.format(
egn=egn, car_plate_num=identifier)

return await self.__get_obligations_from_url(url, identifier_type, identifier, external_httpx_client)

async def get_obligations_business(
Expand All @@ -225,4 +239,4 @@ async def get_obligations_business(
url = _URL_BUSINESS.format(
egn=egn, identifier=govt_id, bulstat=bulstat)

return await self.__get_obligations_from_url(url, PersonalDocumentType.NATIONAL_ID, govt_id, external_httpx_client)
return await self.__get_obligations_from_url(url, PersonalIdentificationType.NATIONAL_ID, govt_id, external_httpx_client)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kat_bulgaria"
version = "3.0.0"
version = "3.1.0"
description = "A library to check for existing obligations from KAT Bulgaria"
readme = "README.md"
license = { text = "MIT" }
Expand Down
15 changes: 12 additions & 3 deletions sample_usage_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import asyncio

from kat_bulgaria.kat_api_client import KatApiClient
from kat_bulgaria.data_models import PersonalDocumentType
from kat_bulgaria.data_models import PersonalIdentificationType
from kat_bulgaria.errors import KatError, KatErrorType, KatErrorSubtype


Expand All @@ -14,7 +14,7 @@ async def sample_code():
# Проверка за физически лица - лична карта:
obligations = await KatApiClient().get_obligations_individual(
egn="валидно_егн",
identifier_type=PersonalDocumentType.NATIONAL_ID,
identifier_type=PersonalIdentificationType.NATIONAL_ID,
identifier="номер_лична_карта"
)
print(f"Брой задължения - ФЛ/ЛК: {len(obligations)}\n")
Expand All @@ -23,12 +23,21 @@ async def sample_code():
# Проверка за физически лица - шофьорска книжка:
obligations = await KatApiClient().get_obligations_individual(
egn="валидно_егн",
identifier_type=PersonalDocumentType.DRIVING_LICENSE,
identifier_type=PersonalIdentificationType.DRIVING_LICENSE,
identifier="номер_шофьорска_книжка"
)
print(f"Брой задължения - ФЛ/ШК: {len(obligations)}\n")
print(f"Raw JSON: {obligations}\n")

# Проверка за физически лица - номер на автомобил (латиница, без интервали):
obligations = await KatApiClient().get_obligations_individual(
egn="валидно_егн",
identifier_type=PersonalIdentificationType.CAR_PLATE_NUM,
identifier="номер_автомобил_латиница_без_интервали"
)
print(f"Брой задължения - ФЛ/ШК: {len(obligations)}\n")
print(f"Raw JSON: {obligations}\n")

# Проверка за юридически лица - лична карта:
obligations = await KatApiClient().get_obligations_business(
egn="валидно_егн",
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
LICENSE = "123456789"
GOV_ID = "AA1234567"
BULSTAT = "000000000"
CAR_PLATE = "OB4444AP"

INVALID_EGN = "9988776655"
INVALID_LICENSE = "123"
INVALID_GOV_ID = "999"
INVALID_BULSTAT = "321"
INVALID_CAR_PLATE = "Б 0000 ББ"


def load_html(local_path: str):
Expand Down
Loading