Skip to content
This repository has been archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
Issue #2: Imitation of sending letters
Browse files Browse the repository at this point in the history
  • Loading branch information
lexhouk committed Aug 3, 2024
1 parent 2d3e970 commit 7b8a386
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 52 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Quotes

Issue #1
[Issue #1](/lexhouk/goit-pyweb-hw-08/issues/1)

## Deployment

Expand All @@ -16,11 +16,17 @@ $ python search.py

# Delivery

Issue #2
[Issue #2](/lexhouk/goit-pyweb-hw-08/issues/2)

## Deployment

```bash
$ docker run -d --name lexhouk-hw-08 -p 5672:5672 -p 15672:15672 rabbitmq:3.13.6-management-alpine
$ python producer.py
```

## Usage

```bash
$ python consumer.py
```
38 changes: 16 additions & 22 deletions connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
from logging import basicConfig, error, INFO
from pathlib import Path

from mongoengine import connect
from mongoengine.connection import ConnectionFailure
from pymongo.errors import ConfigurationError


def absent(type: str, name: str = None) -> str:
'''Show warning message.
Expand All @@ -19,41 +15,39 @@ def absent(type: str, name: str = None) -> str:
return f'{type}{name} not found.'


def init() -> bool:
def init(is_rabbitmq: bool = False) -> bool:
basicConfig(format='%(levelname)s: %(message)s', level=INFO)

path = Path('credentials.ini')
section = 'rabbitmq' if is_rabbitmq else 'mongodb'

try:
if not path.exists():
raise Exception(absent('Configuration file', path))

(config := ConfigParser()).read(path, 'utf-8')

options = ['user', 'password', 'host']

if is_rabbitmq:
options.append('port')

options.append('name')

try:
credentials = [
config.get('database', option)
for option in ('user', 'password', 'host', 'name')
]
credentials = [config.get(section, option) for option in options]
except NoSectionError as err:
raise Exception(absent('Section', err.section))
except NoOptionError as err:
raise Exception(absent('Option', err.option))
else:
URI = 'mongodb+srv://{}:{}@{}/?retryWrites=true&w=majority'

try:
connect(
db=credentials.pop(),
host=URI.format(*credentials),
tls=True,
tlsAllowInvalidCertificates=True
)
except (ConfigurationError, ConnectionFailure):
raise Exception('Invalid credentials.')
if is_rabbitmq:
from services.rabbitmq.connect import handler
else:
from services.mongodb.connect import handler

return handler(credentials)
except Exception as err:
error(err)

return False

return True
47 changes: 47 additions & 0 deletions consumer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from logging import info
from sys import exit

from pika.adapters.blocking_connection import BlockingChannel

from connect import init
from services.rabbitmq.models import Contact


def send(email: str) -> bool:
'''Stub function.
:param email: An E-mail address.
:return: True if a letter has been sent successfully.
'''
...


def callback(channel: BlockingChannel, method, properties, body) -> None:
if (contact := Contact.objects(id=body.decode(), delivered=False).first()):
info(f'Sending letter to {contact.email}...')

send(contact.email)

contact.update(set__delivered=True)


def main() -> None:
if not init() or not (data := init(True)):
return

_, channel, queue_name = data

channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue_name, callback, True)

info('Waiting for messages. Press CTRL+C to exit.')

channel.start_consuming()


if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
exit(0)
30 changes: 4 additions & 26 deletions producer.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
from pickle import dumps
from random import randint

from faker import Faker
from mongoengine import Document
from mongoengine.fields import BooleanField, EmailField, StringField
from pika import BlockingConnection, ConnectionParameters, PlainCredentials

from connect import init


class Contact(Document):
fullname = StringField(max_length=50, required=True)
email = EmailField(required=True)
address = StringField(max_length=200)
delivered = BooleanField(required=True, default=False)
meta = {'collection': 'contacts'}
from services.rabbitmq.models import Contact


def main() -> None:
if not init():
if not init() or not (data := init(True)):
return

connection = BlockingConnection(
ConnectionParameters(
'localhost',
5672,
credentials=PlainCredentials('guest', 'guest')
)
)

QUEUE = 'subscribers'

(channel := connection.channel()).queue_declare(QUEUE)

connection, channel, queue_name = data
fake = Faker()

for _ in range(randint(10, 20)):
Expand All @@ -42,7 +20,7 @@ def main() -> None:
address=fake.address()
).save()

channel.basic_publish('', QUEUE, dumps(contact))
channel.basic_publish('', queue_name, str(contact.id).encode())

connection.close()

Expand Down
2 changes: 1 addition & 1 deletion search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any

from connect import absent, init
from models import Author, Quote
from services.mongodb.models import Author, Quote


def response(caption: str, names: list, field: list, value: Any) -> str:
Expand Down
2 changes: 1 addition & 1 deletion seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from connect import absent, init

# Each model should be imported to use them from global definitions.
from models import Author, Quote
from services.mongodb.models import Author, Quote


def collection(
Expand Down
19 changes: 19 additions & 0 deletions services/mongodb/connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from mongoengine import connect
from mongoengine.connection import ConnectionFailure
from pymongo.errors import ConfigurationError


def handler(credentials: list) -> bool:
URI = 'mongodb+srv://{}:{}@{}/?retryWrites=true&w=majority'

try:
connect(
db=credentials.pop(),
host=URI.format(*credentials),
tls=True,
tlsAllowInvalidCertificates=True
)
except (ConfigurationError, ConnectionFailure):
raise Exception('Invalid credentials.')

return True
File renamed without changes.
16 changes: 16 additions & 0 deletions services/rabbitmq/connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pika import BlockingConnection, ConnectionParameters, PlainCredentials


def handler(credentials: list) -> tuple:
connection = BlockingConnection(
ConnectionParameters(
credentials[2],
credentials[3],
credentials=PlainCredentials(credentials[0], credentials[1]),
),
)

channel = connection.channel()
channel.queue_declare(credentials[4], durable=True)

return connection, channel, credentials[4]
10 changes: 10 additions & 0 deletions services/rabbitmq/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from mongoengine import Document
from mongoengine.fields import BooleanField, EmailField, StringField


class Contact(Document):
fullname = StringField(max_length=50, required=True)
email = EmailField(required=True)
address = StringField(max_length=200)
delivered = BooleanField(required=True, default=False)
meta = {'collection': 'contacts'}

0 comments on commit 7b8a386

Please sign in to comment.