TL;DR

Because of another project, I started to work directly with the AEMPS CIMA API.
It was not intuitive at all and I wanted to start with a personal library that would work as a wrapper for the API.

Here I will present my first PIP package. Giving context on how queries are created, error handling and the orchestration of existing modules within my library, among many other curiosities.

https://github.com/fqlenos/aempsconn
https://pypi.org/project/aempsconn/

Introduction

The project started as a quick script that would make my life easier.
It was not at all my intention to share it with the outside world, however, today I see myself writing a blog post. :)

I’m not going to focus on what this CIMA API is used for, but on how it works and the meaning I wanted to give to the library.

In case one day you also decide to create one!

My structure

The following is the structure of the project:

.
├── LICENSE
├── README.md
├── aempsconn
│   ├── __init__.py
│   ├── datatypes
│   │   ├── __init__.py
│   │   ├── datatypes.py
│   ├── decorators
│   │   ├── __init__.py
│   │   ├── http_res_handler.py
│   │   └── json_res_handler.py
│   ├── errors
│   │   ├── __init__.py
│   │   └── errors.py
│   ├── filter
│   │   ├── __init__.py
│   │   ├── filter.py
│   │   ├── filter_medicamento.py
│   │   └── filter_medicamentos.py
│   ├── logger
│   │   ├── __init__.py
│   │   ├── custom_logger.py
│   │   └── formatter.py
│   ├── modules
│   │   ├── __init__.py
│   │   ├── base.py
│   │   └── cima
│   │       ├── __init__.py
│   │       ├── medicamento.py
│   │       └── medicamentos.py
│   ├── orchestrate
│   │   ├── __init__.py
│   │   └── orchestrate.py
│   └── utils
│       ├── __init__.py
│       ├── config.py
│       ├── endpoint.py
│       └── signal_handler.py
├── poetry.lock
├── pyproject.toml
├── requirements.txt
└── tests
    ├── __init__.py
    ├── decorators
    │   ├── __init__.py
    │   ├── test_http_res_handler.py
    │   └── test_json_res_handler.py
    ├── filter
    │   ├── __init__.py
    │   ├── test_filter_medicamento.py
    │   └── test_filter_medicamentos.py
    ├── modules
    │   ├── __init__.py
    │   └── cima
    │       ├── __init__.py
    │       ├── test_medicamento.py
    │       └── test_medicamentos.py
    ├── orchestrate
    │   ├── __init__.py
    │   └── test_orchestrate.py
    └── utils
        ├── __init__.py
        └── test_signal_handler.py

I am not going to follow the same order that appears on the previous tree, I will prioritize those that I consider the most important when creating a library.

./aempsconn/errors/

No doubt this is one of the key points of libraries.

In my opinion, you can’t leave it up to the end-user to handle the errors that may arise with exceptions from another library (e.g., requests.exceptions).

Whether it’s because the request is broken or because your proxy is misconfigured, you always have to offer your user completely customized error handling.

Because I also don’t think it’s good practice to let the program break on its own for whatever reason. If it breaks, let it be because you asked it to.
Demonstrate that you are in control of what happens in your code.

On the other hand, you may have not taken into account some exception, simply because many “trial and error” of the library are still missing.
That does not mean that you cannot raise a custom generic exception to warn the user that it is an exception that you had never contemplated, so it can be handled too now.

You can even encourage them to open a issue on your project to add improvements! Contributing always feels good.

Custom exceptions

Once that said, you can now use your custom exceptions as I do:

import aempsconn

try:
    # do something

except aempsconn.errors.HTTPFailure:
    # Custom exception related to the HTTP requests.

except aempsconn.errors.JSONDecodeFailure:
    # Custom exception related to JSON decode.

except aempsconn.errors.JSONKeyFailure:
    # Custom exception related to JSON dict's keys.

except aempsconn.errors.ProxyFailure:
    # Custom exception related to the proxy configuration.

except aempsconn.errors.RequestFailure:
    # Custom exception related to the Python3 request module.

except aempsconn.errors.TimeoutFailure:
    # Custom exception related to the timeout.

except aempsconn.errors.UnhandledError:
    # Custom exception related to unhandled exceptions.

./aempsconn/filter/

The main idea is that the programmer who makes use of this can enjoy the library in his code in the way that best suits his way of programming, as far as possible.

Therefore, with the filter, I offer the possibility of generating objects of type Filter that carry inside everything necessary for the HTTP request that is behind.

Thus, you can generate as many filters as you want, and then make the requests you want and how you want without suffering race conditions within the library, among others.
I also think it gains a more structured and logical point to structure the code.

These filters have been structured to be created using a Query Builder as intuitive as possible and forcing the data typing.
Attached below is an example:

import aempsconn
aemps = aempsconn.Orchestrate()

filter_ex_1 = aemps.filter_medicamento.num_registro.equals("59494")

filter_ex_2 = (
    aemps.filter_medicamentos.nombre.equals("paraceta*")
    .comercializado.equals(True)
    .laboratorio.equals("cinfa")
    .receta.equals(True)
)

As it is seen, each condition extends a .equals(value=value) which only allows its specific data type.

./aempsconn/modules/

The AEMPS has a specific API for the CIMA, but it also has another one for the CIMA VET and another one for the REEC.
Inisito, I don’t think it makes sense to talk about the difference between them, although if you are curious I leave here the official information about them: APIs.

This is important to explain why the modules/, and is that granulating the project in this way, there will always be a chance to keep growing without potential scalability errors.
Each endpoint of each API will always fall under its module, in this case as they all belong to CIMA, they are located under: ./aempsconn/modules/cima/.

Who knows, maybe one day I will make the wrapper for all your endpoints!

./aempsconn/orchestrate/

As mentioned in the previous section, there are several modules in the AEMPS. This creates the need to centralise the initialisation of each module separately in my code.

As I don’t want the end-user to have to know which and how to initialise the modules, I have created an Orchestrate class that will take care of all this, as shown in the following code (./orchestrate/orchestrate.py):

class Orchestrate:
    def __init__(...):
        # ...

        # Related to CIMA
        self.filter_medicament = FilterMedicament(config=config)
        self.filter_medicines = FilterMedicines(config=config)
        self.medicament = cima.Medicament(config=config)
        self.medicament = cima.Medicament(config=config)

This way, when we initialise the Orchestrate() from our main program, it will preload the modules: filter_medicamento, filter_medicamentos, medicamento and medicamentos.

./aempsconn/logger/

OK, there’s always a lot of dispute here:
– A library shouldn’t have a Logger
++ A library should have a Logger

You both win. During development I felt it was necessary to know where my requests were breaking, so I included a temporal Logger that would allow me to do better debugging.

Once I was done with the most annoying part, I decided to give the end-user the option to decide whether or not to use the Logger.
For the same reason, I wanted to allow them to use their personal Logger, with their favourite format, or to use the Custom Logger built in this package.

To make it clearer.
If you do not want to use any kind of Logger:

import aempsconn

aemps = aempsconn.Orchestrate()

If you want to make use of my Custom Logger:

import aempsconn

aemps = aempsconn.Orchestrate(logger=aempsconn.CustomLogger())

Finally, if you want to make use of your own Logger:

import aempsconn
import logging

# Define your own Logger
logger = logging.Logger()

aemps = aempsconn.Orchestrate(logger=logger)

./aempsconn/datatypes/

I don’t think there is much to comment at this point… All the data types that the library needs are set here.

What I do want to emphasize is that to make objects for data types you should use Pydantic, there is no easier and more professional way to validate data types and create solid structures for your project.

./aempsconn/decorators/

Decorators don’t have to be strictly necessary, in my case, it made some sense to make use of them.

If the library you have in mind also makes HTTP requests, you might want to take a look at the decorator found under ./http_res_handler.py.

Publishing your package

Assuming that you already have a PyPI account created, create an API Token.

For building and publishing, I use poetry for the simplicity it provides.

We start poetry in your current folder:

cd ./your-package
poetry init

After updating the pyproject.toml file with all the necessary data, load the previous API token into our poetry configuration:

poetry config pypi-token.pypi <api-token>

Check that everything is the way you really want it and then all that’s left is to build and publish:

poetry build
poetry publish

You can find more information about this in the official documentation.

Yay! You have it!