RabbitMQ - zamaniamin/python GitHub Wiki

When and for what purpose do we use RabbitMQ ?

We use RabbitMQ as a message broker or message-oriented middleware to enable communication between different applications or services in a distributed system. It is particularly useful in scenarios where multiple systems or components need to exchange data or messages asynchronously and reliably. RabbitMQ provides a flexible and scalable messaging platform that can handle different messaging patterns and protocols, including point-to-point, publish/subscribe, request/response, and routing. Some common use cases for RabbitMQ include:

  1. Microservices architecture
  2. Real-time data processing
  3. Distributed computing and parallel processing
  4. Event-driven systems and workflows
  5. Task scheduling and background processing
  6. Cloud-native applications and services
  7. IoT and sensor networks
  8. Chat and messaging applications
  9. Financial transactions and trading systems
  10. Enterprise application integration.

1. What is RabbitMQ, and what are its main features?

RabbitMQ is an open-source message broker software that facilitates communication between distributed systems. It implements Advanced Message Queuing Protocol (AMQP) and can be used with various programming languages, including Python. Some of the main features of RabbitMQ are:

  1. Support for multiple messaging protocols like AMQP, MQTT, and STOMP.
  2. High availability and clustering.
  3. Support for message persistence.
  4. Flexible routing and messaging patterns.
  5. Extensive plugins and extensions support.
  6. Reliable message delivery and acknowledgments.
  7. Easy integration with various programming languages, including Python.

2. How do you install and configure RabbitMQ for use with Python?

To install RabbitMQ for use with Python, you can follow these steps:

  1. Install RabbitMQ on your system. This can typically be done using your system's package manager, or by downloading the appropriate package from the RabbitMQ website.

  2. Install the pika Python library using pip, which is the official RabbitMQ client library for Python. You can do this by running the following command:

    pip install pika
    
  3. Import the pika library in your Python code to start using RabbitMQ.

Once you have installed and configured RabbitMQ, you can use Python to interact with it and perform various tasks such as sending and receiving messages, creating and managing queues, and more.

3. What are exchanges, queues, and bindings in RabbitMQ, and how do they work together?

In RabbitMQ, exchanges, queues, and bindings are the three main components that work together to enable message routing and delivery.

An exchange is a message routing agent that receives messages from producers and pushes them to queues. It accepts messages with a certain routing key and then forwards them to one or many bound queues. Exchanges have several types, including direct, fanout, topic, and headers.

A queue is a message buffer that holds messages until they are consumed by consumers. Each message is placed in a specific queue based on its routing key. Consumers can subscribe to one or many queues to receive messages.

Bindings connect exchanges to queues and define the rules for message routing. They specify a routing key that determines which messages will be delivered to which queues. When an exchange receives a message, it uses the binding rules to determine which queues to forward the message to.

Overall, exchanges, queues, and bindings work together to provide a flexible and powerful messaging system that can be used in a variety of applications.

4. What is a message in RabbitMQ, and how is it structured?

In RabbitMQ, a message is a piece of data that is sent by a producer to a consumer through a broker. The message consists of two parts: a message header, which contains metadata about the message, and a message body, which contains the actual data being sent. The message header includes information such as the message routing key, delivery mode, message priority, and expiration time. The message body can contain any type of data that can be serialized, such as JSON or binary data. The message is stored in a queue until a consumer is ready to receive and process it. Once a consumer receives the message, it can be acknowledged by the consumer, which removes the message from the queue.

5. How do you publish a message to a RabbitMQ exchange in Python?

In Python, you can publish a message to a RabbitMQ exchange using the pika library, which is the most popular Python client for RabbitMQ. Here's an example code snippet that shows how to publish a message to an exchange using pika:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Create an exchange
channel.exchange_declare(exchange='my_exchange', exchange_type='direct')

# Publish a message to the exchange
channel.basic_publish(
    exchange='my_exchange',
    routing_key='my_routing_key',
    body='Hello, RabbitMQ!'
)

# Close the connection
connection.close()

In this example, we first create a connection and a channel to RabbitMQ. Then, we declare an exchange called my_exchange with a type of direct. Finally, we publish a message with the body 'Hello, RabbitMQ!' to the exchange with the routing key my_routing_key. Once the message is published, we close the connection.

6. How do you consume messages from a RabbitMQ queue in Python?

To consume messages from a RabbitMQ queue in Python, you can use the pika library, which is a pure-Python implementation of the RabbitMQ messaging protocol. Here is an example of how to consume messages from a queue:

import pika

# Establish a connection to the RabbitMQ server
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare the queue to consume from
channel.queue_declare(queue='my_queue')

# Define a callback function to handle incoming messages
def callback(ch, method, properties, body):
    print("Received message:", body.decode())

# Start consuming messages from the queue
channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=True)
print("Waiting for messages...")
channel.start_consuming()

In this example, we first establish a connection to the RabbitMQ server using the BlockingConnection class from the pika library. Then, we create a channel object that we will use to interact with RabbitMQ.

Next, we declare the queue that we want to consume from using the queue_declare method. This ensures that the queue exists and is ready to receive messages.

We define a callback function named callback that will be called each time a message is received from the queue. The function takes four parameters: ch (the channel object), method (information about the message, such as the delivery tag and routing key), properties (metadata about the message, such as its content type), and body (the actual message payload).

Finally, we use the basic_consume method to start consuming messages from the queue. The on_message_callback parameter specifies the callback function to use for incoming messages. The auto_ack parameter is set to True to automatically acknowledge messages as they are received. The start_consuming method is called to begin consuming messages and block the current thread until the connection is closed or the stop_consuming method is called.

7. What is a consumer in RabbitMQ, and how is it implemented in Python?

In RabbitMQ, a consumer is a client application that subscribes to and receives messages from a queue. In Python, the pika library provides a way to implement a consumer using the BlockingConnection class.

To implement a consumer in Python using pika, you can define a callback function that will be executed every time a message is received. The function will be passed the message as a parameter, and you can define what the function does with the message.

Here's an example of a simple consumer in Python using pika:

import pika

def callback(ch, method, properties, body):
    print("Received message:", body)

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='my_queue')

channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=True)

print('Waiting for messages...')
channel.start_consuming()

In this example, the callback function simply prints out the message body when a message is received. The channel.basic_consume() method is used to subscribe to the my_queue queue and call the callback function every time a message is received. The auto_ack=True parameter means that the message will be acknowledged automatically once it is received.

The channel.start_consuming() method is used to start the consumer and begin waiting for messages.

8. What is a producer in RabbitMQ, and how is it implemented in Python?

A producer in RabbitMQ is a program that sends messages to a RabbitMQ message broker. In Python, you can implement a RabbitMQ producer using the pika library, which is a pure-Python implementation of the AMQP protocol used by RabbitMQ.

Here's an example of how to create a simple RabbitMQ producer in Python using pika:

import pika

# Connect to the RabbitMQ broker
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare the exchange to which messages will be sent
channel.exchange_declare(exchange='my_exchange', exchange_type='direct')

# Send a message to the exchange
channel.basic_publish(exchange='my_exchange', routing_key='my_key', body='Hello, world!')

# Close the connection
connection.close()

In this example, we first connect to the RabbitMQ broker running on the local machine using pika.BlockingConnection(). We then create a channel using connection.channel(), which is where all message-related operations take place.

Next, we declare an exchange using channel.exchange_declare(). The exchange is named "my_exchange" and is of type "direct", which means that messages will be routed to queues based on their routing key.

Finally, we publish a message to the exchange using channel.basic_publish(). The message contains the string "Hello, world!" and is routed to the queue with the routing key "my_key".

After the message is sent, we close the connection using connection.close().

9. What is a durable queue in RabbitMQ, and how is it created in Python?

A durable queue is a type of RabbitMQ queue that survives even if the RabbitMQ broker is restarted. This means that messages in a durable queue are not lost due to broker failure. Here's how to create a durable queue in RabbitMQ using Python:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# Declare a durable queue
channel.queue_declare(queue='my_durable_queue', durable=True)

# Publish a message to the durable queue
channel.basic_publish(exchange='', routing_key='my_durable_queue', body='Hello, world!', properties=pika.BasicProperties(delivery_mode=2))

connection.close()

In the queue_declare method, the durable parameter is set to True to create a durable queue. When publishing a message to a durable queue, the delivery_mode property of the pika.BasicProperties class is set to 2, which indicates that the message should be persisted to disk.

10. What is a dead letter exchange in RabbitMQ, and how is it configured in Python?

In RabbitMQ, a dead letter exchange (DLX) is an exchange to which messages are sent when they are not delivered to the intended destination queue. Dead letter exchanges are useful in scenarios where messages cannot be delivered due to errors, such as when a queue is full or when the message is rejected by the consumer.

To configure a dead letter exchange in Python, you can create a new exchange with the type "fanout" and bind it to the queue you want to use as the dead letter queue. You can then set the "x-dead-letter-exchange" argument on the original queue to the name of the dead letter exchange, and set the "x-dead-letter-routing-key" argument to the routing key to be used for messages that are sent to the dead letter exchange.

Here's an example:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare the dead letter exchange
channel.exchange_declare(exchange='my_dl_exchange', exchange_type='fanout')

# Declare the queue with dead letter settings
channel.queue_declare(queue='my_queue', arguments={
    'x-dead-letter-exchange': 'my_dl_exchange',
    'x-dead-letter-routing-key': 'my_queue.dl'
})

# Bind the dead letter exchange to the dead letter queue
channel.queue_bind(queue='my_dl_queue', exchange='my_dl_exchange')

# Publish a message to the queue
channel.basic_publish(exchange='', routing_key='my_queue', body='Hello, world!')

In this example, we create a dead letter exchange called "my_dl_exchange" and a queue called "my_queue" with dead letter settings. We then bind the dead letter exchange to a queue called "my_dl_queue". Finally, we publish a message to the "my_queue" queue. If the message cannot be delivered to the "my_queue" queue, it will be sent to the "my_dl_exchange" exchange and then to the "my_dl_queue" queue.

11. What is a topic exchange in RabbitMQ, and how is it used in Python?

A topic exchange in RabbitMQ is a type of exchange that routes messages based on a matching pattern that is specified in the message routing key. The routing key is a string that is attached to the message by the publisher, and the topic exchange uses the routing key to determine which queues to deliver the message to. The topic exchange supports wildcard matching using the characters * and #, allowing for flexible routing based on message content.

In Python, a topic exchange can be created using the pika library, which provides a simple and convenient API for working with RabbitMQ. Here's an example:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# create the topic exchange
channel.exchange_declare(exchange='my_topic_exchange', exchange_type='topic')

# create a queue and bind it to the exchange with a routing key
channel.queue_declare(queue='my_queue')
channel.queue_bind(exchange='my_topic_exchange', queue='my_queue', routing_key='example.*')

# publish a message with a routing key
channel.basic_publish(exchange='my_topic_exchange', routing_key='example.foo', body='Hello, World!')

# consume messages from the queue
def callback(ch, method, properties, body):
    print("Received message:", body)

channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=True)
channel.start_consuming()

In this example, we create a topic exchange named my_topic_exchange, and then create a queue named my_queue and bind it to the exchange with a routing key of example.*. This means that any messages with a routing key that starts with example. will be delivered to the queue.

We then publish a message with a routing key of example.foo, which matches the routing key pattern we defined earlier, and finally consume messages from the queue using a callback function.

12. How do you implement RabbitMQ message acknowledgements in Python?

In RabbitMQ, message acknowledgements are used to inform the broker that a message has been successfully received and processed by the consumer. This ensures that the message is not lost in case the consumer goes down before processing it.

To implement message acknowledgements in Python with the pika package, you need to set the auto_ack parameter to False when consuming messages. This tells the broker that the consumer will acknowledge each message manually. Then, when the consumer is done processing a message, it can send an acknowledgement message back to the broker using the basic_ack method of the channel object.

Here is an example code snippet that demonstrates how to implement message acknowledgements in Python with pika:

import pika

# Establish a connection to the RabbitMQ broker
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare a queue
channel.queue_declare(queue='my_queue', durable=True)

# Define a callback function to process messages
def callback(ch, method, properties, body):
    print("Received message:", body.decode())
    ch.basic_ack(delivery_tag=method.delivery_tag) # Send acknowledgement message

# Start consuming messages from the queue
channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=False)
channel.start_consuming()

In this example, we declare a durable queue named my_queue with durable=True. Then, we define a callback function callback to process each message. The basic_ack method is called at the end of the callback function to send an acknowledgement message back to the broker.

Note that if a message is not acknowledged by the consumer within a certain time period (configured on the broker), it will be considered unacknowledged and will be requeued for delivery to another consumer.

13. What is RabbitMQ clustering, and how is it configured in Python?

RabbitMQ clustering refers to a group of RabbitMQ nodes working together as a single logical broker to improve availability, scalability, and reliability. Clustering provides automatic failover and load balancing by distributing queues and their contents among the available nodes.

To configure clustering in Python, the following steps can be followed:

  1. Install and configure RabbitMQ on each node of the cluster.
  2. Define the same Erlang cookie on all nodes of the cluster to ensure that they can communicate with each other.
  3. Start the RabbitMQ service on all nodes of the cluster.
  4. Create a RabbitMQ policy that defines the HA mode for the queues that need to be clustered.
  5. Create a mirrored queue with the same name and arguments on each node of the cluster.
  6. Add the nodes to the RabbitMQ cluster using the rabbitmqctl command or the web management interface.

Once the cluster is up and running, Python can interact with it using the same methods as a standalone RabbitMQ instance. However, it is recommended to use a load balancer or a DNS round-robin setup to distribute the connections across the nodes in the cluster.

14. What is RabbitMQ federation, and how is it configured in Python?

RabbitMQ Federation is a feature that enables the exchange of messages between brokers located in different geographical locations. It allows the creation of a network of RabbitMQ brokers that can work together as a single system.

Federation can be used to distribute workload across different regions or to implement disaster recovery strategies. It can also be used to create a highly available system by replicating messages across multiple data centers.

To configure RabbitMQ federation in Python, you need to perform the following steps:

  1. Install the rabbitmq_federation plugin by running the following command:
rabbitmq-plugins enable rabbitmq_federation
  1. Configure the upstream broker by defining a new upstream set that specifies the URL of the remote broker. This can be done using the RabbitMQ Management UI or by running the following command:
rabbitmqctl set_parameter federation-upstream <name> '{"uri": "<URL>", "expires": "3600000"}'

where <name> is the name of the upstream set and <URL> is the URL of the remote broker.

  1. Configure the downstream broker by defining a new federation policy that specifies the upstream set and the exchange to federate. This can be done using the RabbitMQ Management UI or by running the following command:
rabbitmqctl set_policy --apply-to exchanges <name> "^<exchange>$" '{"federation-upstream-set": "<upstream_set>"}'

where <name> is the name of the federation policy, <exchange> is the name of the exchange to federate, and <upstream_set> is the name of the upstream set.

  1. Start the federation by running the following command:
rabbitmqctl start_app

Once the federation is set up, messages published to the federated exchange on the downstream broker will be replicated to the upstream broker and vice versa.

15. What is RabbitMQ SSL/TLS encryption, and how is it implemented in Python?

RabbitMQ supports SSL/TLS encryption for secure communication between clients and brokers. To implement SSL/TLS encryption in Python, you need to configure the RabbitMQ broker and use the appropriate Python libraries to establish a secure connection.

Here are the steps to implement SSL/TLS encryption in RabbitMQ using Python:

  1. Generate SSL/TLS certificates: You can generate self-signed SSL/TLS certificates using OpenSSL. These certificates are used to encrypt the traffic between clients and brokers. You can generate a private key and a certificate signing request (CSR) using OpenSSL, and then generate a self-signed certificate using the private key and CSR.

  2. Configure the RabbitMQ broker: Once you have the SSL/TLS certificates, you need to configure the RabbitMQ broker to use SSL/TLS encryption. You can do this by updating the RabbitMQ configuration file and specifying the path to the SSL/TLS certificates.

  3. Update the Python code: Finally, you need to update your Python code to use SSL/TLS encryption to connect to the RabbitMQ broker. You can use the pika library, which is a Python client for RabbitMQ, to establish a secure connection. When connecting to the broker, you need to specify the SSL/TLS options, such as the path to the SSL/TLS certificates and the version of SSL/TLS to use.

Here is an example Python code to establish an SSL/TLS encrypted connection to a RabbitMQ broker using pika library:

import pika
import ssl

context = ssl.create_default_context(cafile="/path/to/ca_certificate.pem")
ssl_options = pika.SSLOptions(context, "rabbitmq-server")

connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host="localhost",
        port=5671,
        ssl_options=ssl_options
    )
)

In this example, we create an SSL context using the path to the CA certificate file. We then create pika.SSLOptions object using the SSL context and the hostname of the RabbitMQ broker. Finally, we create a pika.BlockingConnection object using the SSL options and connect to the broker.

16. What is RabbitMQ monitoring, and how is it performed in Python?

RabbitMQ provides a built-in management interface that allows you to monitor the message broker and its components, such as exchanges, queues, and channels, in real-time. You can also view statistics and metrics on message rates, connections, and consumers, among other things.

In addition to the built-in management interface, there are also several tools and plugins available for RabbitMQ monitoring, such as:

  1. Prometheus: A popular open-source monitoring and alerting toolkit that can be used to collect and store metrics from RabbitMQ.

  2. Grafana: An open-source data visualization tool that can be used to create dashboards and graphs from the metrics collected by Prometheus.

  3. RabbitMQ Exporter: A Prometheus exporter that exposes RabbitMQ metrics in a format that can be scraped by Prometheus.

  4. RabbitMQ Management Plugin: A plugin that provides an HTTP-based API for querying RabbitMQ statistics and metrics.

To use these tools and plugins in Python, you would typically install them as dependencies in your project and configure them to connect to your RabbitMQ instance. Then, you can use the collected data to monitor the performance and health of your RabbitMQ deployment.

17. What is the RabbitMQ Management Plugin, and how is it used in Python?

The RabbitMQ Management Plugin is a web-based interface for managing RabbitMQ servers. It provides a dashboard that allows you to monitor the status of the RabbitMQ server, manage exchanges, queues, and bindings, view message rates and queue depths, and configure users, permissions, and plugins.

To use the RabbitMQ Management Plugin in Python, you can make use of the pika library which provides a Python client for RabbitMQ. The pika library includes support for the Management Plugin API, which allows you to interact with the RabbitMQ Management Plugin programmatically.

Here's an example of how you can use the pika library to interact with the RabbitMQ Management Plugin API in Python:

import pika

# Connect to RabbitMQ server
credentials = pika.PlainCredentials('guest', 'guest')
parameters = pika.ConnectionParameters('localhost', 5672, '/', credentials)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()

# Get list of queues
response = channel.queue_declare(queue='', exclusive=True)
queue_name = response.method.queue
channel.queue_bind(queue=queue_name, exchange='amq.rabbitmq.management', routing_key='queue.list')
channel.basic_publish(exchange='amq.rabbitmq.management', routing_key='queue.list', body='')
for method_frame, properties, body in channel.consume(queue_name):
    print(body.decode('utf-8'))
    channel.basic_ack(method_frame.delivery_tag)
    break
channel.cancel()
connection.close()

In this example, we connect to the RabbitMQ server using pika, declare a queue, bind it to the queue.list routing key on the amq.rabbitmq.management exchange, and publish a message to the exchange. We then consume the message from the queue, which contains a list of all queues on the server, and print it to the console. Finally, we acknowledge the message and cancel the consumer, and close the connection to the RabbitMQ server.

18. What is RabbitMQ RPC, and how is it implemented in Python?

RabbitMQ Remote Procedure Call (RPC) is a messaging pattern used for building distributed systems where a client sends a request message to a server and waits for a response message. It allows for a distributed system to operate as if it were a single, monolithic system, with clients sending requests and servers responding to them.

In RabbitMQ, RPC is implemented using a queue for requests and a queue for responses. The client sends a request message to the request queue and waits for a response on the response queue. The server listens on the request queue, receives the request message, processes it, and sends a response message to the response queue.

To implement RPC in Python with RabbitMQ, you can use the pika library, which is a Python client for RabbitMQ. Here's an example of how to implement RPC in Python with pika:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

result = channel.queue_declare(queue='', exclusive=True)
callback_queue = result.method.queue

def on_response(ch, method, props, body):
    if props.correlation_id == corr_id:
        response = body.decode('utf-8')
        print(f"Received response: {response}")
        connection.close()

channel.basic_consume(
    queue=callback_queue,
    on_message_callback=on_response,
    auto_ack=True)

message = "Hello, World!"
corr_id = str(uuid.uuid4())
channel.basic_publish(
    exchange='',
    routing_key='rpc_queue',
    properties=pika.BasicProperties(
        reply_to=callback_queue,
        correlation_id=corr_id,
    ),
    body=message)

print(f"Sent message: {message}")
channel.start_consuming()

In this example, the client sends a message to a queue called rpc_queue with a correlation ID and a reply-to queue. The server listens on the rpc_queue, receives the request, processes it, and sends a response back to the reply-to queue specified in the request. The client waits for a response on its reply-to queue and prints the received response.

19. What are some common RabbitMQ performance tuning techniques in Python?

There are several techniques to optimize the performance of RabbitMQ in Python. Here are some common ones:

  1. Increase the number of RabbitMQ worker processes to handle more messages concurrently.
  2. Use durable queues and messages to ensure messages are not lost in case of server failure.
  3. Use message acknowledgements to confirm message delivery and avoid message duplication.
  4. Implement message prefetching to prevent worker processes from being overwhelmed with too many messages at once.
  5. Avoid using transactions if possible, as they can reduce message throughput.
  6. Configure RabbitMQ to limit message sizes to avoid message fragmentation and performance degradation.
  7. Use the RabbitMQ Management Plugin to monitor server performance and identify bottlenecks.
  8. Use connection pooling to reduce the overhead of creating and destroying connections to RabbitMQ.
  9. Consider implementing load balancing and clustering to distribute message processing across multiple servers.
  10. Optimize the serialization and deserialization of messages to reduce processing overhead.

By applying these techniques, you can improve the performance and scalability of RabbitMQ in your Python applications.

20. How do you integrate RabbitMQ with other Python frameworks and libraries, such as Django or Flask?

Integrating RabbitMQ with Python frameworks and libraries is a common use case, and it can be achieved through a variety of approaches. Here are some general steps to integrate RabbitMQ with popular Python frameworks like Django and Flask:

Django:

  1. Install the pika library using pip.

  2. Create a RabbitMQ connection configuration in your Django settings.py file, specifying the connection parameters such as hostname, port, username, password, and virtual host.

  3. Create a consumer class in your Django application that extends pika.adapters.BlockingConnection and implements the message handling logic.

  4. Use the consumer class in your Django view to receive messages from the RabbitMQ queue.

  5. Use the pika library to publish messages to a RabbitMQ exchange or queue from your Django application.

Flask:

  1. Install the pika library using pip.

  2. Create a RabbitMQ connection configuration in your Flask config.py file, specifying the connection parameters such as hostname, port, username, password, and virtual host.

  3. Create a consumer function in your Flask application that uses the pika library to consume messages from a RabbitMQ queue.

  4. Use a task queue like Celery to process the messages received from the RabbitMQ queue asynchronously.

  5. Use the pika library to publish messages to a RabbitMQ exchange or queue from your Flask application.

In general, RabbitMQ can be integrated with Python frameworks and libraries using a variety of approaches, such as message queues, task queues, and RPC. It's important to choose the appropriate integration approach based on the requirements of your application.

⚠️ **GitHub.com Fallback** ⚠️