Skip to content

Commit

Permalink
Merge pull request #8 from daltemen/release/0.1.0
Browse files Browse the repository at this point in the history
Release/0.1.0
  • Loading branch information
daltemen authored Jun 22, 2020
2 parents 86b98e6 + 0fa6fd4 commit f175c87
Show file tree
Hide file tree
Showing 31 changed files with 1,011 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Tracked because this service won't be in production environment

# == Databases Credentials
DB_PASSWORD=secret
DB_NAME=transactions
DB_USERNAME=root
DB_HOST=transactions-db

6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ coverage.xml
# Django stuff:
*.log
local_settings.py
db.sqlite3
# db.sqlite3 for users
db.sqlite3-journal

# Flask stuff:
Expand Down Expand Up @@ -102,7 +102,6 @@ celerybeat.pid
*.sage.py

# Environments
.env
.venv
env/
venv/
Expand All @@ -127,3 +126,6 @@ dmypy.json

# Pyre type checker
.pyre/

# ide
.idea
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3

ENV PYTHONUNBUFFERED 1

RUN mkdir /code

WORKDIR /code

COPY requirements.txt /code/

RUN pip install -r requirements.txt

COPY . /code/
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# transactions-alchemy-django
Django with Alchemy Mysql Pandas Rest Framework example

# Components

Users: handling with sqlite (default db with django)
Processors: handling with SQLAlchemy

# Authentication and Credentials
Please use these credentials to test

username: admin

password: admin

This project use Basic Auth (It won't be in production) please take in mind

# Considerations
for django and django rest it's easier handle users with django users system then
for practicality db.sqlite3 has been tracked to have a user to tests, although
for the processors system works with sqlalchemy.

### Prerequisites

to run this project you need:
* Docker and docker compose

### Run with docker
To run just run

```bash
docker-compose up
```

### Run tests
Tests don't have dependencies (They must not have) then you can run if you have
python>=3.7 and requirements.txt dependencies with virtualenv

```console
pytest -s -v
```

or if you prefer docker

TODO: tests with docker

# Curl Quickly Functional Tests

## Process File

It will relate transactions to user authenticated (i.e. admin)

Process File

`POST http://localhost:8000/v1/processors/files`

You can use `transactions_example.csv` of this repository to test

**Auth required** : YES (Basic Auth)

**Response** `http status code 204`

**Example**

```console
curl --request POST \
--url http://localhost:8000/v1/processors/files \
--header 'authorization: Basic YWRtaW46YWRtaW4=' \
--header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
--form file=<path of your file>
```

## Get Transactions by User

It will retrieve transactions related to user authenticated (i.e. admin)
only supports search by transaction_id

Process File

`GET http://localhost:8000/v1/processors/transactions?limit=10&page=1&order_by=id&search=52fba4fa`

**Auth required** : YES (Basic Auth)

**Response** Get Example

```json
{
"count": 6,
"page": 1,
"next_page": 2,
"transactions": [
{
"id": "0b6c0f19-3915-4fd7-93ca-c2be13b3939e",
"transaction_id": "52fba4fa-3a01-4961-a809-e343dd4f9597",
"transaction_date": "2020-06-01",
"transaction_amount": 10000,
"client_id": 1067,
"client_name": "nombre cliente",
"file_id": "d65c6de6-6423-4a97-a707-64382281c4a1",
"user_id": 1
}
]
}
```

**Example**

```console
curl --request GET \
--url 'http://localhost:8000/v1/processors/transactions?limit=10&page=1&order_by=id&search=word' \
--header 'authorization: Basic YWRtaW46YWRtaW4='
```

Binary file added db.sqlite3
Binary file not shown.
36 changes: 36 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3.7'

services:
transactions-db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
environment:
MYSQL_ROOT_PASSWORD: $DB_PASSWORD
MYSQL_DATABASE: $DB_NAME
networks:
- transactions_net
ports:
- 3307:3306
transactions-api:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- 8000:8000
env_file:
- .env
depends_on:
- transactions-db
networks:
- transactions_net

networks:
transactions_net:
name: transactions_net
driver: bridge
volumes:
db_data:
21 changes: 21 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'transactions.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == '__main__':
main()
Empty file added processors/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions processors/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
13 changes: 13 additions & 0 deletions processors/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.apps import AppConfig
from processors.models import Base
from transactions.settings import engine


class ProcessorsConfig(AppConfig):
name = "processors"

def ready(self):
try:
Base.metadata.create_all(engine)
except Exception as e:
print(e)
58 changes: 58 additions & 0 deletions processors/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import datetime
from dataclasses import dataclass
from typing import Optional, List

from pandas import DataFrame


@dataclass
class TransactionFrame:
"""
Transaction class in Frame Repr for domain objects
"""

data_frame: DataFrame


@dataclass
class Transaction:
"""
Transaction class for domain objects used by get
"""

id: str
transaction_id: str
transaction_date: str
transaction_amount: int
client_id: int
client_name: str
file_id: str
user_id: int


@dataclass
class TransactionList:
transactions: List[Transaction]
count: int
page: int
next_page: int


@dataclass
class PaginatorDomain:
page: int
limit: int
order_by: str
search: str


@dataclass
class File:
"""
File class for domain objects
"""

filename: str
user_id: int
id: Optional[str] = None
created_at: Optional[datetime.date] = None
87 changes: 87 additions & 0 deletions processors/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List

from pandas import DataFrame

from processors.domain import (
File,
TransactionFrame,
Transaction,
PaginatorDomain,
TransactionList,
)
from processors.repository import ProcessorDBInterface


@dataclass
class FileInput:
"""
File Input Boundaries Of Manager
"""

name: str
file_data_frame: DataFrame
user_id: int


@dataclass
class ListerHelper:
"""
Helper to handle pagination, ordering and filtering
"""

page: int
limit: int
order_by: str
search: str


@dataclass
class Paginator:
count: int
page: int
next_page: int


@dataclass
class TransactionsOut(Paginator):
transactions: List[Transaction]


class ProcessorManagerInterface(ABC):
"""
Interface to handle access to db
"""

@abstractmethod
def process_file(self, file_input: FileInput) -> bool:
pass

@abstractmethod
def list_transactions(self, lister: ListerHelper, user_id: str) -> TransactionsOut:
pass


class ProcessorManager(ProcessorManagerInterface):
def __init__(self, repo_db: ProcessorDBInterface):
self.repository: ProcessorDBInterface = repo_db

def process_file(self, file_input: FileInput) -> bool:
file = self.repository.create_file(
File(filename=file_input.name, user_id=file_input.user_id)
)
return self.repository.create_transactions(
TransactionFrame(file_input.file_data_frame), file.id, file_input.user_id
)

def list_transactions(self, lister: ListerHelper, user_id: int) -> TransactionsOut:
result: TransactionList = self.repository.get_transactions_by_user_id(
PaginatorDomain(**lister.__dict__), user_id
)
return TransactionsOut(
count=result.count,
page=result.page,
next_page=result.next_page,
transactions=result.transactions,
)
Empty file.
Loading

0 comments on commit f175c87

Please sign in to comment.