Neste projeto nós vamos utilizar a Programação Orientada a Objetos, implementando um gerador de relatórios que recebe como entrada arquivos com dados de um estoque e gera, como saída, um relatório acerca destes dados.
Esses dados de estoque poderão ser obtidos de diversas fontes:
-
Através da importação de um arquivo
CSV
; -
Através da importação de um arquivo
JSON
; -
Através da importação de um arquivo
XML
;
Além disso, o relatório final deverá poder ser gerado em duas versões: simples e completa.
- Clone o repositório
$ git clone https://github.com/AndersonSilva94/project-inventory-report.git
- Entre na pasta do repositório que você acabou de clonar:
$ cd project-inventory-report
- Crie o ambiente virtual para o projeto
$ python3 -m venv .venv && source .venv/bin/activate
Nesse projeto, você será capaz de:
- Você vai aprender sobre paradigmas de programação
- Conceitos de OO na prática, criando classes e instâncias
- Leitura e escria de arquivos
Após implementar o requisito bônus, seu programa deverá ser executável via linha de comando com o comando inventory_report <argumento1> <argumento2>
:
-
O <argumento 1> deve receber o caminho de um arquivo a ser importado. O arquivo pode ser um
csv
,json
ouxml
. -
O <argumento 2> pode receber duas strings:
simples
oucompleto
, cada uma gerando o respectivo relatório.
Este repositório já contém um template com a estrutura de diretórios e arquivos, tanto de código quanto de teste criados. Veja abaixo:
.
├── dev-requirements.txt
├── inventory_report
│ ├── data
│ │ ├── inventory.csv
│ │ ├── inventory.json
│ │ └── inventory.xml
│ ├── importer
│ │ ├── csv_importer.py
│ │ ├── importer.py
│ │ ├── json_importer.py
│ │ └── xml_importer.py
│ ├── inventory
│ │ ├── inventory_iterator.py
│ │ └── inventory.py
│ ├── main.py
│ └── reports
│ ├── complete_report.py
│ └── simple_report.py
├── pyproject.toml
├── README.md
├── requirements.txt
├── setup.cfg
├── setup.py
└── tests
├── __init__.py
├── test_complete_report.py
├── test_csv_importer.py
├── test_importer.py
├── test_inventory.py
├── test_json_importer.py
├── test_main.py
├── test_simple_report.py
└── test_xml_importer.py
Apesar do projeto já possuir uma estrutura base, você quem deve implementar as classes. Novos arquivos podem ser criados conforme a necessidade.
Para executar os testes, lembre-se de primeiro criar e ativar o ambiente virtual, além de também instalar as dependências do projeto. Isso pode ser feito através dos comandos:
$ python3 -m venv .venv
$ source .venv/bin/activate
$ python3 -m pip install -r dev-requirements.txt
O arquivo dev-requirements.txt
contém todos as dependências que serão utilizadas no projeto, ele está agindo como se fosse um package.json
de um projeto Node.js
. Com as dependências já instaladas, para executar os testes basta usar o comando:
$ python3 -m pytest
Se quiser saber mais sobre a instalação de dependências com pip
, veja esse artigo: https://medium.com/python-pandemonium/better-python-dependency-and-package-management-b5d8ea29dff1
Para verificar se você está seguindo o guia de estilo do Python corretamente, você pode executá-lo com o seguinte comando:
$ python3 -m flake8
Arquivos de exemplo nos três formatos de importação estão disponíveis no diretório data
dentro do diretório inventory_report
.
Os arquivos CSV são separados por vírgula, como no exemplo abaixo:
id,nome_do_produto,nome_da_empresa,data_de_fabricacao,data_de_validade,numero_de_serie,instrucoes_de_armazenamento
1,Nicotine Polacrilex,Target Corporation,2020-02-18,2022-09-17,CR25 1551 4467 2549 4402 1,morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate justo in blandit
2,fentanyl citrate,"Galena Biopharma, Inc.",2019-12-06,2022-12-25,FR29 5951 7573 74OY XKGX 6CSG D20,bibendum morbi non quam nec dui luctus rutrum nulla tellus in
3,NITROUS OXIDE,Keen Compressed Gas Co. Inc.,2019-12-22,2023-11-07,CZ09 8588 0858 8435 9140 2695,ipsum dolor sit amet consectetuer adipiscing elit proin risus praesent
Os arquivos JSON seguem o seguinte modelo:
[
{
"id":1,
"nome_do_produto":"CALENDULA OFFICINALIS FLOWERING TOP, GERANIUM MACULATUM ROOT, SODIUM CHLORIDE, THUJA OCCIDENTALIS LEAFY TWIG, ZINC, and ECHINACEA ANGUSTIFOLIA",
"nome_da_empresa":"Forces of Nature",
"data_de_fabricacao":"2020-07-04",
"data_de_validade":"2023-02-09",
"numero_de_serie":"FR48 2002 7680 97V4 W6FO LEBT 081",
"instrucoes_de_armazenamento":"in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus"
}
]
Os arquivos XML seguem o seguinte modelo:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<record>
<id>1</id>
<nome_do_produto>valsartan and hydrochlorothiazide</nome_do_produto>
<nome_da_empresa>Lake Erie Medical & Surgical Supply DBA Quality Care Products LLC</nome_da_empresa>
<data_de_fabricacao>2019-10-27</data_de_fabricacao>
<data_de_validade>2022-08-31</data_de_validade>
<numero_de_serie>MT08 VVDN 2131 9NFL C1JG KTDV RS1L LOZ</numero_de_serie>
<instrucoes_de_armazenamento>at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat</instrucoes_de_armazenamento>
</record>
</dataset>
1 - Criar um método generate
numa classe SimpleReport
do módulo inventory_report/reports/simple_report.py
. Esse método deverá receber dados numa lista contendo estruturas do tipo dict
e deverá retornar uma string formatada como um relatório.
-
Deve ser possível executar o método
generate
sem instanciar um objeto deSimpleReport
-
O método deve receber de parâmetro uma lista de dicionários no seguinte formato:
[ { "id": 1, "nome_do_produto": "CALENDULA OFFICINALIS FLOWERING TOP, GERANIUM MACULATUM ROOT, SODIUM CHLORIDE, THUJA OCCIDENTALIS LEAFY TWIG, ZINC, and ECHINACEA ANGUSTIFOLIA", "nome_da_empresa": "Forces of Nature", "data_de_fabricacao": "2020-07-04", "data_de_validade": "2023-02-09", "numero_de_serie": "FR48 2002 7680 97V4 W6FO LEBT 081", "instrucoes_de_armazenamento": "in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus" } ]
-
O método deverá retornar uma saída com o seguinte formato:
Data de fabricação mais antiga: YYYY-MM-DD Data de validade mais próxima: YYYY-MM-DD Empresa com maior quantidade de produtos estocados: NOME DA EMPRESA
-
A data de validade mais próxima, somente considera itens que ainda não venceram.
Dica: O módulo datetime vai te ajudar.
-
1.1 - Será validado que é possível que o método
generate
da classeSimpleReport
retorne a data de fabricação mais antiga -
1.2 - Será validado que é possível que o método
generate
da classeSimpleReport
retorne a validade mais próxima -
1.3 - Será validado que é possível que o método
generate
da classeSimpleReport
retorne a empresa com maior estoque -
1.4 - Será validado que é possível que o método
generate
da classeSimpleReport
retorne o relatório no formato correto
2 - Criar um método generate
numa classe CompleteReport
do módulo inventory_report/reports/complete_report.py
. Esse método deverá receber dados numa lista contendo estruturas do tipo dict
e deverá retornar uma string formatada como um relatório.
-
A classe
CompleteReport
deve herdar o método (generate
) da classeSimpleReport
, de modo a especializar seu comportamento. -
O método deve receber de parâmetro uma lista de dicionários no seguinte formato:
[ { "id": 1, "nome_do_produto": "CALENDULA OFFICINALIS FLOWERING TOP, GERANIUM MACULATUM ROOT, SODIUM CHLORIDE, THUJA OCCIDENTALIS LEAFY TWIG, ZINC, and ECHINACEA ANGUSTIFOLIA", "nome_da_empresa": "Forces of Nature", "data_de_fabricacao": "2020-07-04", "data_de_validade": "2023-02-09", "numero_de_serie": "FR48 2002 7680 97V4 W6FO LEBT 081", "instrucoes_de_armazenamento": "in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus" } ]
-
O método deverá retornar uma saída com o seguinte formato:
Data de fabricação mais antiga: YYYY-MM-DD Data de validade mais próxima: YYYY-MM-DD Empresa com maior quantidade de produtos estocados: NOME DA EMPRESA Produtos estocados por empresa: - Physicians Total Care, Inc.: QUANTIDADE - Newton Laboratories, Inc.: QUANTIDADE - Forces of Nature: QUANTIDADE
-
2.1 - Será validado que é possível que o método
generate
da classeCompleteReport
retorne a data de fabricação mais antiga -
2.2 - Será validado que é possível que o método
generate
da classeCompleteReport
retorne a validade de fabricação mais próxima -
2.3 - Será validado que é possível que o método
generate
da classeCompleteReport
retorne a empresa com maior estoque -
2.4 - Será validado que é possível que o método
generate
da classeCompleteReport
retorne a quantidade de produtos por empresa -
2.5 - Será validado que é possível que o método
generate
da classeCompleteReport
retorne o relatório no formato correto
3 - Criar um método import_data
dentro de uma classe Inventory
do módulo inventory_report/inventory/inventory.py
, capaz de ler um arquivo CSV o qual o caminho é passado como parâmetro.
- O método, receberá como parâmetro o caminho para o arquivo CSV e o tipo de relatório a ser gerado (
"simples"
,"completo"
). De acordo com os parâmetros recebidos, deve recuperar os dados do arquivo e chamar o método de gerar relatório correspondente à entrada passada. Ou seja, o método da classeInventory
deve chamar o métodogenerate
da classe que vai gerar o relatório (SimpleReport
,CompleteReport
).
-
3.1 - Será validado que ao importar um arquivo csv simples será retornado com sucesso
-
3.2 - Será validado que ao importar um arquivo csv completo será retornado com sucesso
4 - Criar um método import_data
dentro de uma classe Inventory
do módulo inventory_report/inventory/inventory.py
, capaz de ler um arquivo JSON o qual o caminho é passado como parâmetro.
- O método, receberá como parâmetro o caminho para o arquivo JSON e o tipo de relatório a ser gerado (
"simples"
,"completo"
). De acordo com os parâmetros recebidos, deve recuperar os dados do arquivo e chamar o método de gerar relatório correspondente à entrada passada. Ou seja, o método da classeInventory
deve chamar o métodogenerate
da classe que vai gerar o relatório (SimpleReport
,CompleteReport
).
📌 Atente que estamos utilizando o mesmo método do requisito anterior.
-
4.1 - Será validado que ao importar um arquivo json simples será retornado com sucesso
-
4.2 - Será validado que ao importar um arquivo json completo será retornado com sucesso
5 - Criar um método import_data
dentro de uma classe Inventory
do módulo inventory_report/inventory/inventory.py
, capaz de ler um arquivo XML o qual o caminho é passado como parâmetro.
- O método, receberá como parâmetro o caminho para o arquivo XML e o tipo de relatório a ser gerado (
"simples"
,"completo"
). De acordo com os parâmetros recebidos, deve recuperar os dados do arquivo e chamar o método de gerar relatório correspondente à entrada passada. Ou seja, o método da classeInventory
deve chamar o métodogenerate
da classe que vai gerar o relatório (SimpleReport
,CompleteReport
).
📌 Atente que estamos utilizando o mesmo método do requisito anterior.
-
5.1 - Será validado que ao importar um arquivo xml simples será retornado com sucesso
-
5.2 - Será validado que ao importar um arquivo xml completo será retornado com sucesso
6 - Criar uma classe abstrata Importer
no módulo inventory_report/importer/importer.py
, que terá três classes herdeiras: CsvImporter
, JsonImporter
e XmlImporter
, cada uma definida em seu respectivo módulo.
-
A classe abstrata deve definir a assinatura do método
import_data
a ser implementado por cada classe herdeira. Ela deve receber como parâmetro o nome do arquivo a ser importado. -
O método
import_data
definido por cada classe herdeira deve lançar uma exceção caso a extensão do arquivo passado por parâmetro seja inválida. Por exemplo, quando se passa um caminho de um arquivo extensão CSV para oJsonImporter
. -
O método deverá ler os dados do arquivo passado e retorná-los estruturados em uma lista de dicionários conforme exemplo abaixo:
[ { "id": 1, "nome_do_produto": "CALENDULA OFFICINALIS FLOWERING TOP, GERANIUM MACULATUM ROOT, SODIUM CHLORIDE, THUJA OCCIDENTALIS LEAFY TWIG, ZINC, and ECHINACEA ANGUSTIFOLIA", "nome_da_empresa": "Forces of Nature", "data_de_fabricacao": "2020-07-04", "data_de_validade": "2023-02-09", "numero_de_serie": "FR48 2002 7680 97V4 W6FO LEBT 081", "instrucoes_de_armazenamento": "in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus" } ]
-
6.1 - Será validado que a casse CsvImporter está herdando a classe Importer
-
6.2 - Será validado que a casse JsonImporter está herdando a classe Importer
-
6.3 - Será validado que a casse XmlImporter está herdando a classe Importer
-
6.4 - Será validado que a classe CsvImporter esta importando os dados para uma lista
-
6.5 - Será validado que a classe JsonImporter esta importando os dados para uma lista
-
6.6 - Será validado que a classe XmlImporter esta importando os dados para uma lista
-
6.7 - Será validado que ao enviar um arquivo com extensão incorreta para o CsvImporter irá gerar um erro
-
6.8 - Será validado que ao enviar um arquivo com extensão incorreta para o JsonImporter irá gerar um erro
-
6.9 - Será validado que ao enviar um arquivo com extensão incorreta para o XmlImporter irá gerar um erro
👀 Estamos separando a lógica em várias classes (estratégias), preparando para aplicarmos o padrão de projeto Strategy. É uma solução para o caso em que uma classe possui muitas responsabilidades (propósitos).
7 - Criar uma classe InventoryIterator
no módulo inventory_report/inventory/inventory_iterator.py
, que implementa a interface de um iterator (Iterator
). A classe InventoryRefactor
deve implementar o método __iter__
, que retornará este iterador.
-
A classe
Inventory
deverá ser refatorada (copiada) em outro arquivo chamadoinventory_report/inventory/inventory_refactor.py
. Nesse arquivo você irá refatorar a classeInventory
chamando-a deInventoryRefactor
. -
A classe
InventoryRefactor
deve utilizar as classes definidas no requisito 6 para lidar com a lógica de importação, via composição no métodoimport_data
. -
A classe
InventoryRefactor
deve receber por seu construtor a classe que será utilizada para lidar com a lógica de importação e armazenar em um atributo chamadoimporter
. -
As classes
InventoryIterator
eInventoryRefactor
devem implementar corretamente a interface do padrão de projeto Iterator, de modo que seja possível iterar sobre os itens em estoque. -
Ao importar os dados, os mesmos devem ser armazenados na instância, em adição aos itens já presentes naquela instância. O atributo de
InventoryRefactor
que armazena esses dados deve se chamardata
. -
Os atributos e os métodos devem ser públicos.
✍️ Para testar manualmente você pode fazer:
iterator = iter(inventory)
first_item = next(iterator)
-
7.1 - Será validado que a instancia de InventoryRefactor é iterável (Iterable)
-
7.2 - Será validado que é possivel iterar o primeiro item da lista usando csv
-
7.3 - Será validado que é possivel iterar o primeiro item da lista usando json
-
7.4 - Será validado que é possivel iterar o primeiro item da lista usando xml
-
7.5 - Será validado que é possivel receber duas fontes de dados sem sobreescrita
-
7.6 - Será validado que não é possivel enviar arquivo inválido
8 - Preencha a função main
no módulo inventory_report/main.py
que, ao receber pela linha de comando o caminho de um arquivo e o tipo de relatório, devolve o relatório correto.
-
Deverá ser usado a classe
InventoryRefactor
para recuperar os dados e gerar o relatório. -
Ao chamar o comando no formato abaixo pelo terminal, deve ser impresso na tela o devido relatório no formato da saída dos requisitos
1
e2
:
$ inventory_report <caminho_do_arquivo_input> <tipo_de_relatório>
- Caso a chamada tenha menos de três argumentos (o nome
inventory_report
é considerado o primeiro argumento), exiba a mensagem de erro "Verifique os argumentos" nastderr
.
📌 A função sys.argv
deve ser utilizada para receber a entrada de dados da pessoa usuária.
✍️ Teste manual: dentro de um ambiente virtual onde seu projeto foi configurado, digite o comando inventory_report parametro_1 parametro_2
, assim você conseguirá interagir com o menu.
-
8.1 - Será validado se o menu importa um arquivo csv simples
-
8.2 - Será validado se o menu importa um arquivo csv completo
-
8.3 - Será validado se o menu importa um arquivo json simples
-
8.4 - Será validado se o menu importa um arquivo json completo
-
8.5 - Será validado se o menu importa um arquivo xml simples
-
8.6 - Será validado se o menu importa um arquivo xml completo
-
8.7 - Será validado se houverem argumentos faltantes será retornando um erro
⌨️ com 💜 por Anderson Silva (Andy) 😊