Skip to main content

uktrade-hmrc-licensing-management-client

API client for HMRC internal licensing management API

Send licence to HMRC example

import requests.exceptions
from django.core.cache import cache

from hmrc_licensing_management import utils
from hmrc_licensing_management.api import Client, Licence, APIResponse, Result


class CachedClient(Client):
    """Example client used that stores the access token for as long as it's valid."""

    session_key = "CDS_CLIENT_GET_ACCESS_TOKEN_KEY"

    def get_access_token(self, *, scope: str | None = None) -> utils.AccessToken:
        # Look for access token in cache
        token = cache.get(self.session_key, None)

        if not token:
            # Fetch a new token
            token = super().get_access_token(scope=scope)

            # Store token in cache for as long as it's valid
            cache.add(self.session_key, token, timeout=token["expires_in"])

        return token


def example_implementation():
    # 1. Create client (use example class above to cache access token)
    client = CachedClient(
        # Production or sandbox hmrc API url
        hmrc_api_base_url="https://test-api.service.hmrc.gov.uk",
        # Client id and secret retrieved from HMRC API application
        hmrc_api_client_id="test-client-id",
        hmrc_api_client_secret="test-client-secret",
        # TOTP secret required for a HMRC "Privileged application"
        hmrc_api_totp_secret="test-totp-secret",
    )

    # 2. Create a licence payload
    # Licence serializer details omitted as it will be specific to your licence type
    licence_reference = "GBSIL123456"
    licence_data = Licence(...)

    # 3. Send a licence to HMRC
    try:
        api_response: APIResponse = client.send_licence_details(
            licence_reference, licence_data
        )

        if api_response.result == Result.accepted:
            print("Handle accepted licence")
        else:
            print("handle rejected licence")

    # The client can raise HTTP errors with attached notes
    except requests.exceptions.HTTPError as e:
        # Handle any http errors
        ...

    # Handle any unknown errors not explicitly raised by the API client
    except Exception as e:
        ...

All available serializers to create a licence payload can be found here.

NOTE: Do not use serializers found in hmrc_licensing_management/api/_serializers.py.

They have been autogenerated from the OpenAPI spec and have been updated with extra validation.

The serializers have been autogenerated from the licensing management Open API specification.

Several useful constants and utility functions can be found here and here:

Usage data callback example

This library provides a class-based view to subclass for processing usage data.

This is the HMRC API it uses: https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/push-pull-notifications-api/1.0

The licence management api usage data payload is found here:

Shown below is an example implementation with the following:

  • A model to store the incoming data
  • A view that inherits from HMRCPushPullCallbackView and stores the data in HMRCPushPullNotification
import logging

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models, transaction
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

from hmrc_licensing_management.usage import (
    HMRCPushPullCallbackView,
    HMRCPushPullResponseItem,
    NotifyUsage,
)

logger = logging.getLogger(__name__)


#
# Model used to store the usage data (taken from ECIL)
#
class HMRCPushPullNotification(models.Model):
    """Stores data received from HMRC's push pull notification API.

    Example payload to store in model:
        {
        "notificationId": "1ed5f407-8096-40d1-87ef-9a2a103eeb85",  # /PS-IGNORE
        "boxId": "50dca3fc-c37c-4f03-b719-63571333624c",
        "messageContentType": "application/json",
        "message": "[PAYLOAD]",
        "status": "PENDING",
        "createdDateTime": "2020-06-01T10:20:23.160+0000"
        }
    """

    class MessageContentType(models.TextChoices):
        application_json = "application/json"
        application_xml = "application/xml"

    class Status(models.TextChoices):
        pending = "PENDING"
        failed = "FAILED"
        acknowledged = "ACKNOWLEDGED"

    #
    # Fields containing data from HMRC
    #
    notification_id = models.TextField(
        help_text="Unique identifier for a notification."
    )
    box_id = models.TextField(
        help_text="Unique identifier for a box the notification was sent to."
    )
    message_content_type = models.CharField(
        max_length=20,
        choices=MessageContentType.choices,
        help_text="Content type of the message.",
    )
    message = models.JSONField(
        help_text=(
            "The notification message defined by messageContentType (JSON or XML). "
            "If this is JSON then it will have been escaped. "
            "Details on the structure of this data can be found in the documentation for the HMRC "
            "API that created the notification."
        ),
        encoder=DjangoJSONEncoder,
    )
    status = models.CharField(
        max_length=20, choices=Status.choices, help_text="Status of the notification."
    )
    created_datetime = models.DateTimeField(
        help_text="ISO-8601 UTC date and time the notification was created."
    )

    #
    # Fields added for ECIL
    #
    received_at = models.DateTimeField(
        auto_now_add=True,
        help_text="Date and time the notification was received by ECIL.",
    )
    processed = models.BooleanField(
        default=False, help_text="Indicates if the notification was processed."
    )
    processed_at = models.DateTimeField(
        null=True,
        default=None,
        help_text="Date and time the notification was processed.",
    )


#
# View to store the incoming usage data.
#
@method_decorator(csrf_exempt, name="dispatch")
@method_decorator(transaction.atomic, name="post")
class LicenceDetailsUsageCallbackView(HMRCPushPullCallbackView):
    def process_payload(self, payload: HMRCPushPullResponseItem) -> None:
        """Process the incoming usage data payload from HMRC

        Notes from HMRC:
        Design your application to process duplicate push notifications as a single notification (idempotency)

        The push notification system is designed to send ‘At least once’ to guarantee delivery.
        In most cases, this means notifications will be sent once and successfully received.
        In rare cases of network disruption, messages may be sent more than once.
        You should design your application to process duplicate notifications as a single notification.
        This will prevent errors and provide a consistent outcome for your application and users.
        """

        message = NotifyUsage.model_validate_json(payload.message)

        record, created = (
            HMRCPushPullNotification.objects.select_for_update().get_or_create(
                defaults={
                    "box_id": payload.boxId,
                    "message_content_type": payload.messageContentType.value,
                    "message": message.model_dump(
                        exclude_none=True, exclude_unset=True
                    ),
                    "status": payload.status.value,
                    "created_datetime": payload.createdDateTime,
                },
                notification_id=payload.notificationId,
            )
        )

        if created:
            logger.info(
                "HMRCPushPullNotification record created. notification_id: %s",
                payload.notificationId,
            )
        else:
            logger.info(
                "HMRCPushPullNotification record ignored. notification_id: %s",
                payload.notificationId,
            )

        # Do something with the data
        # e.g. trigger a task to do something with the HMRCPushPullNotification record.
        process_hmrc_push_pull_notifications.delay()

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hmrc_licensing_management-1.0.1.tar.gz (81.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hmrc_licensing_management-1.0.1-py3-none-any.whl (40.6 kB view details)

Uploaded Python 3

File details

Details for the file hmrc_licensing_management-1.0.1.tar.gz.

File metadata

  • Download URL: hmrc_licensing_management-1.0.1.tar.gz
  • Upload date:
  • Size: 81.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hmrc_licensing_management-1.0.1.tar.gz
Algorithm Hash digest
SHA256 664a5e2b5f902adbbd9964298a13abfb664123cbf5b61a7f6c0e81bfb7fdb288
MD5 86e187925b943bd933a14a3543cee87b
BLAKE2b-256 f48bcdb22f346ca664e9b157c91ee9d55dccc43ab77d9eb7999d39eea16aa2ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for hmrc_licensing_management-1.0.1.tar.gz:

Publisher: release.yml on uktrade/uktrade-hmrc-licensing-management-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hmrc_licensing_management-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: hmrc_licensing_management-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 40.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hmrc_licensing_management-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2c982c5ee21e7513690bc2956254f57c41ea7497728a95c2d755c8301c4361ec
MD5 1d994dba22bf03c0f37d0426a14586be
BLAKE2b-256 d77c56e9fe6f97af5f34377169c460851e8212b5ec8cce6d77b923214e699e11

See more details on using hashes here.

Provenance

The following attestation bundles were made for hmrc_licensing_management-1.0.1-py3-none-any.whl:

Publisher: release.yml on uktrade/uktrade-hmrc-licensing-management-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page