From a5c904c3e306aa3313518379182c8aa070bfb998 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Sun, 21 Aug 2022 16:00:50 -0300 Subject: [PATCH 01/16] feat: add fundamentos module files --- 00 - Fundamentos/convertendo_tipos.py | 12 ++++ 00 - Fundamentos/desafio.py | 66 +++++++++++++++++++ .../estrutura_condicional_aninhada.py | 29 ++++++++ .../estrutura_condicional_ternaria.py | 6 ++ 00 - Fundamentos/estrutura_repeticao_break.py | 18 +++++ 00 - Fundamentos/estrutura_repeticao_for.py | 15 +++++ 00 - Fundamentos/estrutura_repeticao_while.py | 11 ++++ 00 - Fundamentos/estruturas_condicionais.py | 24 +++++++ 00 - Fundamentos/identacao_blocos.py | 16 +++++ 00 - Fundamentos/operadores_aritmeticos.py | 15 +++++ 00 - Fundamentos/operadores_associacao.py | 6 ++ 00 - Fundamentos/operadores_atribuicao.py | 26 ++++++++ 00 - Fundamentos/operadores_comparacao.py | 9 +++ 00 - Fundamentos/operadores_identidade.py | 5 ++ 00 - Fundamentos/operadores_logicos.py | 26 ++++++++ 00 - Fundamentos/primeiro_programa.py | 1 + 00 - Fundamentos/print_input.py | 7 ++ 00 - Fundamentos/string_1.py | 19 ++++++ 00 - Fundamentos/string_2.py | 22 +++++++ 00 - Fundamentos/string_3.py | 10 +++ 00 - Fundamentos/string_4.py | 24 +++++++ 00 - Fundamentos/tipos_de_dados.py | 5 ++ 00 - Fundamentos/variaveis_constantes.py | 12 ++++ 23 files changed, 384 insertions(+) create mode 100644 00 - Fundamentos/convertendo_tipos.py create mode 100644 00 - Fundamentos/desafio.py create mode 100644 00 - Fundamentos/estrutura_condicional_aninhada.py create mode 100644 00 - Fundamentos/estrutura_condicional_ternaria.py create mode 100644 00 - Fundamentos/estrutura_repeticao_break.py create mode 100644 00 - Fundamentos/estrutura_repeticao_for.py create mode 100644 00 - Fundamentos/estrutura_repeticao_while.py create mode 100644 00 - Fundamentos/estruturas_condicionais.py create mode 100644 00 - Fundamentos/identacao_blocos.py create mode 100644 00 - Fundamentos/operadores_aritmeticos.py create mode 100644 00 - Fundamentos/operadores_associacao.py create mode 100644 00 - Fundamentos/operadores_atribuicao.py create mode 100644 00 - Fundamentos/operadores_comparacao.py create mode 100644 00 - Fundamentos/operadores_identidade.py create mode 100644 00 - Fundamentos/operadores_logicos.py create mode 100644 00 - Fundamentos/primeiro_programa.py create mode 100644 00 - Fundamentos/print_input.py create mode 100644 00 - Fundamentos/string_1.py create mode 100644 00 - Fundamentos/string_2.py create mode 100644 00 - Fundamentos/string_3.py create mode 100644 00 - Fundamentos/string_4.py create mode 100644 00 - Fundamentos/tipos_de_dados.py create mode 100644 00 - Fundamentos/variaveis_constantes.py diff --git a/00 - Fundamentos/convertendo_tipos.py b/00 - Fundamentos/convertendo_tipos.py new file mode 100644 index 000000000..e78f99669 --- /dev/null +++ b/00 - Fundamentos/convertendo_tipos.py @@ -0,0 +1,12 @@ +print(int(1.97348728)) +print(int("10")) +print(float("10.10")) +print(float(100)) + +valor = 10 +valor_str = str(valor) +print(type(valor)) +print(type(valor_str)) + +print(100 / 2) +print(100 // 2) diff --git a/00 - Fundamentos/desafio.py b/00 - Fundamentos/desafio.py new file mode 100644 index 000000000..c2179f64d --- /dev/null +++ b/00 - Fundamentos/desafio.py @@ -0,0 +1,66 @@ +menu = """ + +[d] Depositar +[s] Sacar +[e] Extrato +[q] Sair + +=> """ + +saldo = 0 +limite = 500 +extrato = "" +numero_saques = 0 +LIMITE_SAQUES = 3 + +while True: + + opcao = input(menu) + + if opcao == "d": + valor = float(input("Informe o valor do depósito: ")) + + if valor > 0: + saldo += valor + extrato += f"Depósito: R$ {valor:.2f}\n" + + else: + print("Operação falhou! O valor informado é inválido.") + + elif opcao == "s": + valor = float(input("Informe o valor do saque: ")) + + excedeu_saldo = valor > saldo + + excedeu_limite = valor > limite + + excedeu_saques = numero_saques >= LIMITE_SAQUES + + if excedeu_saldo: + print("Operação falhou! Você não tem saldo suficiente.") + + elif excedeu_limite: + print("Operação falhou! O valor do saque excede o limite.") + + elif excedeu_saques: + print("Operação falhou! Número máximo de saques excedido.") + + elif valor > 0: + saldo -= valor + extrato += f"Saque: R$ {valor:.2f}\n" + numero_saques += 1 + + else: + print("Operação falhou! O valor informado é inválido.") + + elif opcao == "e": + print("\n================ EXTRATO ================") + print("Não foram realizadas movimentações." if not extrato else extrato) + print(f"\nSaldo: R$ {saldo:.2f}") + print("==========================================") + + elif opcao == "q": + break + + else: + print("Operação inválida, por favor selecione novamente a operação desejada.") diff --git a/00 - Fundamentos/estrutura_condicional_aninhada.py b/00 - Fundamentos/estrutura_condicional_aninhada.py new file mode 100644 index 000000000..72c34af7a --- /dev/null +++ b/00 - Fundamentos/estrutura_condicional_aninhada.py @@ -0,0 +1,29 @@ +conta_normal = False +conta_universitaria = False +conta_especial = True + +saldo = 2000 +saque = 1500 +cheque_especial = 450 + +if conta_normal: + + if saldo >= saque: + print("Saque realizado com sucesso!") + elif saque <= (saldo + cheque_especial): + print("Saque realizado com uso do cheque especial!") + else: + print("Não foi possivel realizar o saque, saldo insuficiente!") + +elif conta_universitaria: + + if saldo >= saque: + print("Saque realizado com sucesso!") + else: + print("Saldo insuficiente!") + +elif conta_especial: + print("Conta especial selecionada!") + +else: + print("Sistema não reconheceu seu tipo de conta, entre em contato com o seu gerente.") diff --git a/00 - Fundamentos/estrutura_condicional_ternaria.py b/00 - Fundamentos/estrutura_condicional_ternaria.py new file mode 100644 index 000000000..9c1e95dd5 --- /dev/null +++ b/00 - Fundamentos/estrutura_condicional_ternaria.py @@ -0,0 +1,6 @@ +saldo = 2000 +saque = 2500 + +status = "Sucesso" if saldo >= saque else "Falha" + +print(f"{status} ao realizar o saque!") diff --git a/00 - Fundamentos/estrutura_repeticao_break.py b/00 - Fundamentos/estrutura_repeticao_break.py new file mode 100644 index 000000000..21c90afa6 --- /dev/null +++ b/00 - Fundamentos/estrutura_repeticao_break.py @@ -0,0 +1,18 @@ +while True: + numero = int(input("Informe um número: ")) + + if numero == 10: + break + + if numero % 2 == 0: + continue + + print(numero) + + +# for numero in range(100): + +# if numero % 2 == 0: +# continue + +# print(numero, end=" ") diff --git a/00 - Fundamentos/estrutura_repeticao_for.py b/00 - Fundamentos/estrutura_repeticao_for.py new file mode 100644 index 000000000..c84881aa6 --- /dev/null +++ b/00 - Fundamentos/estrutura_repeticao_for.py @@ -0,0 +1,15 @@ +texto = input("Informe um texto: ") +VOGAIS = "AEIOU" + + +# Exemplo utilizando um iterável +for letra in texto: + if letra.upper() in VOGAIS: + print(letra, end="") +else: + print() # adiciona uma quebra de linha + + +# Exemplo utilizando a função built-in range +for numero in range(0, 51, 5): + print(numero, end=" ") diff --git a/00 - Fundamentos/estrutura_repeticao_while.py b/00 - Fundamentos/estrutura_repeticao_while.py new file mode 100644 index 000000000..2b51b26a6 --- /dev/null +++ b/00 - Fundamentos/estrutura_repeticao_while.py @@ -0,0 +1,11 @@ +opcao = -1 + +while opcao != 0: + opcao = int(input("[1] Sacar \n[2] Extrato \n[0] Sair \n: ")) + + if opcao == 1: + print("Sacando...") + elif opcao == 2: + print("Exibindo o extrato...") +else: + print("Obrigado por usar nosso sistema bancário, até logo!") diff --git a/00 - Fundamentos/estruturas_condicionais.py b/00 - Fundamentos/estruturas_condicionais.py new file mode 100644 index 000000000..2a0f66718 --- /dev/null +++ b/00 - Fundamentos/estruturas_condicionais.py @@ -0,0 +1,24 @@ +MAIOR_IDADE = 18 +IDADE_ESPECIAL = 17 + +idade = int(input("Informe sua idade: ")) + +if idade >= MAIOR_IDADE: + print("Maior de idade, pode tirar a CHN.") + +if idade < MAIOR_IDADE: + print("Ainda não pode tirar a CNH.") + + +if idade >= MAIOR_IDADE: + print("Maior de idade, pode tirar a CHN.") +else: + print("Ainda não pode tirar a CNH.") + + +if idade >= MAIOR_IDADE: + print("Maior de idade, pode tirar a CHN.") +elif idade == IDADE_ESPECIAL: + print("Pode fazer aulas teóricas, mas não pode fazer aulas práticas.") +else: + print("Ainda não pode tirar a CNH.") diff --git a/00 - Fundamentos/identacao_blocos.py b/00 - Fundamentos/identacao_blocos.py new file mode 100644 index 000000000..83e86ccdf --- /dev/null +++ b/00 - Fundamentos/identacao_blocos.py @@ -0,0 +1,16 @@ +def sacar(valor): + saldo = 500 + + if saldo >= valor: + print("valor sacado!") + print("retire o seu dinheiro na boca do caixa.") + + print("Obrigado por ser nosso cliente, tenha um bom dia!") + + +def depositar(valor): + saldo = 500 + saldo += valor + + +sacar(1000) diff --git a/00 - Fundamentos/operadores_aritmeticos.py b/00 - Fundamentos/operadores_aritmeticos.py new file mode 100644 index 000000000..c7e703cbd --- /dev/null +++ b/00 - Fundamentos/operadores_aritmeticos.py @@ -0,0 +1,15 @@ +produto_1 = 20 +produto_2 = 10 + +print(produto_1 + produto_2) +print(produto_1 - produto_2) +print(produto_1 / produto_2) +print(produto_1 // produto_2) +print(produto_1 * produto_2) +print(produto_1 % produto_2) +print(produto_1 ** produto_2) + +x = (10 + 5) * 4 +y = (10 / 2) + 25 * ((2 - 2) ** 2) +print(x) +print(y) diff --git a/00 - Fundamentos/operadores_associacao.py b/00 - Fundamentos/operadores_associacao.py new file mode 100644 index 000000000..03432ca20 --- /dev/null +++ b/00 - Fundamentos/operadores_associacao.py @@ -0,0 +1,6 @@ +frutas = ["limao", "uva"] +curso = "Curso de python" + +print("laranja" not in frutas) +print("limao" in frutas) +print("Python" in curso) diff --git a/00 - Fundamentos/operadores_atribuicao.py b/00 - Fundamentos/operadores_atribuicao.py new file mode 100644 index 000000000..970b77188 --- /dev/null +++ b/00 - Fundamentos/operadores_atribuicao.py @@ -0,0 +1,26 @@ +saldo = 500 +print(saldo) + +saldo = 200 +print(saldo) + +saldo += 10 +print(saldo) + +saldo -= 5 +print(saldo) + +saldo //= 2 +print(saldo) + +saldo /= 2 +print(saldo) + +saldo *= 10 +print(saldo) + +saldo %= 4 +print(saldo) + +saldo **= 2 +print(saldo) diff --git a/00 - Fundamentos/operadores_comparacao.py b/00 - Fundamentos/operadores_comparacao.py new file mode 100644 index 000000000..b408b8869 --- /dev/null +++ b/00 - Fundamentos/operadores_comparacao.py @@ -0,0 +1,9 @@ +saldo = 200 +saque = 200 + +print(saldo == saque) +print(saldo != saque) +print(saldo > saque) +print(saldo >= saque) +print(saldo < saque) +print(saldo <= saque) diff --git a/00 - Fundamentos/operadores_identidade.py b/00 - Fundamentos/operadores_identidade.py new file mode 100644 index 000000000..fcae6aef4 --- /dev/null +++ b/00 - Fundamentos/operadores_identidade.py @@ -0,0 +1,5 @@ +saldo = 1000 +limite = 1000 + +print(saldo is limite) +print(saldo is not limite) diff --git a/00 - Fundamentos/operadores_logicos.py b/00 - Fundamentos/operadores_logicos.py new file mode 100644 index 000000000..003692baf --- /dev/null +++ b/00 - Fundamentos/operadores_logicos.py @@ -0,0 +1,26 @@ +# AND = para ser True tudo tem que ser True +# OR = para ser True apenas um tem que ser True + +print(True and True and True) +print(True and False and True) +print(False and False and False) +print(True or True or True) +print(True or False or False) +print(False or False or False) + +saldo = 1000 +saque = 250 +limite = 200 +conta_especial = True + +exp = saldo >= saque and saque <= limite or conta_especial and saldo >= saque +print(exp) + +exp_2 = (saldo >= saque and saque <= limite) or (conta_especial and saldo >= saque) +print(exp_2) + +conta_normal_com_saldo_suficiente = saldo >= saque and saque <= limite +conta_especial_com_saldo_suficiente = conta_especial and saldo >= saque + +exp_3 = conta_normal_com_saldo_suficiente or conta_especial_com_saldo_suficiente +print(exp_3) diff --git a/00 - Fundamentos/primeiro_programa.py b/00 - Fundamentos/primeiro_programa.py new file mode 100644 index 000000000..1552cf1c9 --- /dev/null +++ b/00 - Fundamentos/primeiro_programa.py @@ -0,0 +1 @@ +print("Oi, seja bem vindo ao curso de Python!") diff --git a/00 - Fundamentos/print_input.py b/00 - Fundamentos/print_input.py new file mode 100644 index 000000000..4d26987e0 --- /dev/null +++ b/00 - Fundamentos/print_input.py @@ -0,0 +1,7 @@ +nome = input("Informe o seu nome: ") +idade = input("Informe a sua idade: ") + +print(nome, idade) +print(nome, idade, end="...\n") +print(nome, idade, sep="#", end="...\n") +print(nome, idade, sep="#") diff --git a/00 - Fundamentos/string_1.py b/00 - Fundamentos/string_1.py new file mode 100644 index 000000000..87bb8151a --- /dev/null +++ b/00 - Fundamentos/string_1.py @@ -0,0 +1,19 @@ +nome = "gUIlherME" + +print(nome.upper()) +print(nome.lower()) +print(nome.title()) + +texto = " Olá mundo! " + +print(texto + ".") +print(texto.strip() + ".") +print(texto.rstrip() + ".") +print(texto.lstrip() + ".") + +menu = "Python" + +print("####" + menu + "####") +print(menu.center(14)) +print(menu.center(14, "#")) +print("-".join(menu)) diff --git a/00 - Fundamentos/string_2.py b/00 - Fundamentos/string_2.py new file mode 100644 index 000000000..53de952e2 --- /dev/null +++ b/00 - Fundamentos/string_2.py @@ -0,0 +1,22 @@ +nome = "Guilherme" +idade = 28 +profissao = "Progamador" +linguagem = "Python" +saldo = 45.435 + +dados = {"nome": "Guilherme", "idade": 28} + +print("Nome: %s Idade: %d" % (nome, idade)) + +print("Nome: {} Idade: {}".format(nome, idade)) + +print("Nome: {1} Idade: {0}".format(idade, nome)) +print("Nome: {1} Idade: {0} Nome: {1} {1}".format(idade, nome)) + +print("Nome: {nome} Idade: {idade}".format(nome=nome, idade=idade)) +print("Nome: {name} Idade: {age} {name} {name} {age}".format(age=idade, name=nome)) +print("Nome: {nome} Idade: {idade}".format(**dados)) + +print(f"Nome: {nome} Idade: {idade}") +print(f"Nome: {nome} Idade: {idade} Saldo: {saldo:.2f}") +print(f"Nome: {nome} Idade: {idade} Saldo: {saldo:10.1f}") diff --git a/00 - Fundamentos/string_3.py b/00 - Fundamentos/string_3.py new file mode 100644 index 000000000..5708f04a8 --- /dev/null +++ b/00 - Fundamentos/string_3.py @@ -0,0 +1,10 @@ +nome = "Guilherme Arthur de Carvalho" + +print(nome[0]) +print(nome[-2]) +print(nome[:9]) +print(nome[10:]) +print(nome[10:16]) +print(nome[10:16:2]) +print(nome[:]) +print(nome[::-1]) diff --git a/00 - Fundamentos/string_4.py b/00 - Fundamentos/string_4.py new file mode 100644 index 000000000..ecda5c9f8 --- /dev/null +++ b/00 - Fundamentos/string_4.py @@ -0,0 +1,24 @@ +nome = "Guilherme" + +mensagem = f""" + Olá meu nome é {nome}, + Eu estou aprendendo Python. + Essa mensagem tem diferentes recuos. +""" + +print(mensagem) + + +print( + """ + ============= MENU ============= + + 1 - Depositar + 2 - Sacar + 0 - Sair + + ================================ + + Obrigado por usar nosso sistema!!!! +""" +) diff --git a/00 - Fundamentos/tipos_de_dados.py b/00 - Fundamentos/tipos_de_dados.py new file mode 100644 index 000000000..4e5b5cb88 --- /dev/null +++ b/00 - Fundamentos/tipos_de_dados.py @@ -0,0 +1,5 @@ +print(11 + 10 + 1000) +print(1.5 + 1 + 0.5) +print(True) +print(False) +print("Python") diff --git a/00 - Fundamentos/variaveis_constantes.py b/00 - Fundamentos/variaveis_constantes.py new file mode 100644 index 000000000..768237807 --- /dev/null +++ b/00 - Fundamentos/variaveis_constantes.py @@ -0,0 +1,12 @@ +nome = "Guilherme" +idade = 28 + +nome, idade = "Giovanna", 27 + +print(nome, idade) + +limite_saque_diario = 1000 + +BRAZILIAN_STATES = ["SP", "RJ", "SC", "RS"] + +print(BRAZILIAN_STATES) From b2fef5ba8b6ec1b82b6a94bc66862d3cf1ffca67 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Mon, 22 Aug 2022 01:54:11 -0300 Subject: [PATCH 02/16] feat: add estruturas de dados module files --- .../01 - Listas/00_declarando_listas.py | 14 +++++++++ .../01 - Listas/01_acesso_direto.py | 4 +++ .../01 - Listas/02_indices_negativos.py | 4 +++ .../01 - Listas/03_matriz.py | 10 ++++++ .../01 - Listas/04_fatiamento.py | 8 +++++ .../01 - Listas/05_iterar_listas.py | 8 +++++ .../01 - Listas/06_compreensao_de_listas.py | 9 ++++++ .../01 - Listas/07_append.py | 7 +++++ .../01 - Listas/08_clear.py | 7 +++++ .../01 - Listas/09_copy.py | 5 +++ .../01 - Listas/10_count.py | 5 +++ .../01 - Listas/11_extend.py | 7 +++++ .../01 - Listas/12_index.py | 4 +++ 01 - Estrutura de dados/01 - Listas/13_pop.py | 6 ++++ .../01 - Listas/14_remove.py | 5 +++ .../01 - Listas/15_reverse.py | 5 +++ .../01 - Listas/16_sort.py | 15 +++++++++ 01 - Estrutura de dados/01 - Listas/17_len.py | 3 ++ .../01 - Listas/18_sorted.py | 4 +++ .../02 - Tuplas/00_declarando_tuplas.py | 15 +++++++++ .../02 - Tuplas/01_acesso_direto.py | 4 +++ .../02 - Tuplas/03_indices_negativos.py | 9 ++++++ .../02 - Tuplas/04_matriz.py | 10 ++++++ .../02 - Tuplas/05_fatiamento.py | 8 +++++ .../02 - Tuplas/06_iterar_tuplas.py | 12 +++++++ .../02 - Tuplas/07_count.py | 10 ++++++ .../02 - Tuplas/08_index.py | 4 +++ 01 - Estrutura de dados/02 - Tuplas/09_len.py | 9 ++++++ .../03 - Conjuntos/00_declarando_conjuntos.py | 8 +++++ .../03 - Conjuntos/01_acessando_dados.py | 5 +++ .../03 - Conjuntos/02_iterar_conjuntos.py | 7 +++++ .../03 - Conjuntos/03_union.py | 5 +++ .../03 - Conjuntos/04_intersection.py | 5 +++ .../03 - Conjuntos/05_difference.py | 8 +++++ .../03 - Conjuntos/06_symmetric_difference.py | 5 +++ .../03 - Conjuntos/07_issubset.py | 8 +++++ .../03 - Conjuntos/08_issuperset.py | 8 +++++ .../03 - Conjuntos/09_isdisjoint.py | 9 ++++++ .../03 - Conjuntos/10_add.py | 10 ++++++ .../03 - Conjuntos/11_clear.py | 7 +++++ .../03 - Conjuntos/12_copy.py | 7 +++++ .../03 - Conjuntos/13_discard.py | 8 +++++ .../03 - Conjuntos/14_pop.py | 6 ++++ .../03 - Conjuntos/15_remove.py | 5 +++ .../03 - Conjuntos/16_len.py | 3 ++ .../03 - Conjuntos/17_in.py | 4 +++ .../00_declarando_dicionarios.py" | 8 +++++ .../01_acessando_dados.py" | 11 +++++++ .../02_dicionarios_aninhados.py" | 9 ++++++ .../03_iterando_dicionarios.py" | 14 +++++++++ .../04 - Dicion\303\241rios/04_clear.py" | 9 ++++++ .../04 - Dicion\303\241rios/05_copy.py" | 8 +++++ .../04 - Dicion\303\241rios/06_fromkeys.py" | 5 +++ .../04 - Dicion\303\241rios/07_get.py" | 14 +++++++++ .../04 - Dicion\303\241rios/08_items.py" | 4 +++ .../04 - Dicion\303\241rios/09_keys.py" | 4 +++ .../04 - Dicion\303\241rios/10_pop.py" | 7 +++++ .../04 - Dicion\303\241rios/11_popitem.py" | 6 ++++ .../04 - Dicion\303\241rios/12_setdefault.py" | 7 +++++ .../04 - Dicion\303\241rios/13_update.py" | 8 +++++ .../04 - Dicion\303\241rios/14_values.py" | 11 +++++++ .../04 - Dicion\303\241rios/15_in.py" | 18 +++++++++++ .../04 - Dicion\303\241rios/16_del.py" | 12 +++++++ .../00_primeira_funcao.py" | 16 ++++++++++ .../01_retorno_da_funcao.py" | 13 ++++++++ .../02_argumentos_nomeados.py" | 8 +++++ .../03_args_kwargs.py" | 31 +++++++++++++++++++ ...04_parametros_somente_por_posicao copy.py" | 6 ++++ .../05_parametros_somente_por_nome.py" | 6 ++++ .../06_objetos_de_primeira_classe.py" | 10 ++++++ .../07_escopo_local_e_global.py" | 10 ++++++ 71 files changed, 584 insertions(+) create mode 100644 01 - Estrutura de dados/01 - Listas/00_declarando_listas.py create mode 100644 01 - Estrutura de dados/01 - Listas/01_acesso_direto.py create mode 100644 01 - Estrutura de dados/01 - Listas/02_indices_negativos.py create mode 100644 01 - Estrutura de dados/01 - Listas/03_matriz.py create mode 100644 01 - Estrutura de dados/01 - Listas/04_fatiamento.py create mode 100644 01 - Estrutura de dados/01 - Listas/05_iterar_listas.py create mode 100644 01 - Estrutura de dados/01 - Listas/06_compreensao_de_listas.py create mode 100644 01 - Estrutura de dados/01 - Listas/07_append.py create mode 100644 01 - Estrutura de dados/01 - Listas/08_clear.py create mode 100644 01 - Estrutura de dados/01 - Listas/09_copy.py create mode 100644 01 - Estrutura de dados/01 - Listas/10_count.py create mode 100644 01 - Estrutura de dados/01 - Listas/11_extend.py create mode 100644 01 - Estrutura de dados/01 - Listas/12_index.py create mode 100644 01 - Estrutura de dados/01 - Listas/13_pop.py create mode 100644 01 - Estrutura de dados/01 - Listas/14_remove.py create mode 100644 01 - Estrutura de dados/01 - Listas/15_reverse.py create mode 100644 01 - Estrutura de dados/01 - Listas/16_sort.py create mode 100644 01 - Estrutura de dados/01 - Listas/17_len.py create mode 100644 01 - Estrutura de dados/01 - Listas/18_sorted.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/00_declarando_tuplas.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/01_acesso_direto.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/03_indices_negativos.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/04_matriz.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/05_fatiamento.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/06_iterar_tuplas.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/07_count.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/08_index.py create mode 100644 01 - Estrutura de dados/02 - Tuplas/09_len.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/00_declarando_conjuntos.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/01_acessando_dados.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/02_iterar_conjuntos.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/03_union.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/04_intersection.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/05_difference.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/06_symmetric_difference.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/07_issubset.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/08_issuperset.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/09_isdisjoint.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/10_add.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/11_clear.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/12_copy.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/13_discard.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/14_pop.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/15_remove.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/16_len.py create mode 100644 01 - Estrutura de dados/03 - Conjuntos/17_in.py create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/00_declarando_dicionarios.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/01_acessando_dados.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/02_dicionarios_aninhados.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/03_iterando_dicionarios.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/04_clear.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/05_copy.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/06_fromkeys.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/07_get.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/08_items.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/09_keys.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/10_pop.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/11_popitem.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/12_setdefault.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/13_update.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/14_values.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/15_in.py" create mode 100644 "01 - Estrutura de dados/04 - Dicion\303\241rios/16_del.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/00_primeira_funcao.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/01_retorno_da_funcao.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/02_argumentos_nomeados.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/03_args_kwargs.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/04_parametros_somente_por_posicao copy.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/05_parametros_somente_por_nome.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/06_objetos_de_primeira_classe.py" create mode 100644 "01 - Estrutura de dados/05 - Fun\303\247\303\265es/07_escopo_local_e_global.py" diff --git a/01 - Estrutura de dados/01 - Listas/00_declarando_listas.py b/01 - Estrutura de dados/01 - Listas/00_declarando_listas.py new file mode 100644 index 000000000..a9473648a --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/00_declarando_listas.py @@ -0,0 +1,14 @@ +frutas = ["laranja", "maca", "uva"] +print(frutas) + +frutas = [] +print(frutas) + +letras = list("python") +print(letras) + +numeros = list(range(10)) +print(numeros) + +carro = ["Ferrari", "F8", 4200000, 2020, 2900, "São Paulo", True] +print(carro) diff --git a/01 - Estrutura de dados/01 - Listas/01_acesso_direto.py b/01 - Estrutura de dados/01 - Listas/01_acesso_direto.py new file mode 100644 index 000000000..48abce310 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/01_acesso_direto.py @@ -0,0 +1,4 @@ +frutas = ["maçã", "laranja", "uva", "pera"] + +print(frutas[0]) # maçã +print(frutas[2]) # uva diff --git a/01 - Estrutura de dados/01 - Listas/02_indices_negativos.py b/01 - Estrutura de dados/01 - Listas/02_indices_negativos.py new file mode 100644 index 000000000..bc1ef3e8d --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/02_indices_negativos.py @@ -0,0 +1,4 @@ +frutas = ["maçã", "laranja", "uva", "pera"] + +print(frutas[-1]) # pera +print(frutas[-3]) # laranja diff --git a/01 - Estrutura de dados/01 - Listas/03_matriz.py b/01 - Estrutura de dados/01 - Listas/03_matriz.py new file mode 100644 index 000000000..e66efea8b --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/03_matriz.py @@ -0,0 +1,10 @@ +matriz = [ + [1, "a", 2], + ["b", 3, 4], + [6, 5, "c"] +] + +print(matriz[0]) # [1, "a", 2] +print(matriz[0][0]) # 1 +print(matriz[0][-1]) # 2 +print(matriz[-1][-1]) # "c" diff --git a/01 - Estrutura de dados/01 - Listas/04_fatiamento.py b/01 - Estrutura de dados/01 - Listas/04_fatiamento.py new file mode 100644 index 000000000..089d2763b --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/04_fatiamento.py @@ -0,0 +1,8 @@ +lista = ["p", "y", "t", "h", "o", "n"] + +print(lista[2:]) # ["t", "h", "o", "n"] +print(lista[:2]) # ["p", "y"] +print(lista[1:3]) # ["y", "t"] +print(lista[0:3:2]) # ["p", "t"] +print(lista[::]) # ["p", "y", "t", "h", "o", "n"] +print(lista[::-1]) # ["n", "o", "h", "t", "y", "p"] diff --git a/01 - Estrutura de dados/01 - Listas/05_iterar_listas.py b/01 - Estrutura de dados/01 - Listas/05_iterar_listas.py new file mode 100644 index 000000000..f8992f6cd --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/05_iterar_listas.py @@ -0,0 +1,8 @@ +carros = ["gol", "celta", "palio"] + +for carro in carros: + print(carro) + + +for indice, carro in enumerate(carros): + print(f"{indice}: {carro}") diff --git a/01 - Estrutura de dados/01 - Listas/06_compreensao_de_listas.py b/01 - Estrutura de dados/01 - Listas/06_compreensao_de_listas.py new file mode 100644 index 000000000..6f064a19b --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/06_compreensao_de_listas.py @@ -0,0 +1,9 @@ +# Filtrar lista +numeros = [1, 30, 21, 2, 9, 65, 34] +pares = [numero for numero in numeros if numero % 2 == 0] +print(pares) + +# Modificar valores +numeros = [1, 30, 21, 2, 9, 65, 34] +quadrado = [numero**2 for numero in numeros] +print(quadrado) diff --git a/01 - Estrutura de dados/01 - Listas/07_append.py b/01 - Estrutura de dados/01 - Listas/07_append.py new file mode 100644 index 000000000..f6c883271 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/07_append.py @@ -0,0 +1,7 @@ +lista = [] + +lista.append(1) +lista.append("Python") +lista.append([40, 30, 20]) + +print(lista) # [1, "Python", [40, 30, 20]] diff --git a/01 - Estrutura de dados/01 - Listas/08_clear.py b/01 - Estrutura de dados/01 - Listas/08_clear.py new file mode 100644 index 000000000..82a1159fd --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/08_clear.py @@ -0,0 +1,7 @@ +lista = [1, "Python", [40, 30, 20]] + +print(lista) # [1, "Python", [40, 30, 20]] + +lista.clear() + +print(lista) # [] diff --git a/01 - Estrutura de dados/01 - Listas/09_copy.py b/01 - Estrutura de dados/01 - Listas/09_copy.py new file mode 100644 index 000000000..537b37218 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/09_copy.py @@ -0,0 +1,5 @@ +lista = [1, "Python", [40, 30, 20]] + +lista.copy() + +print(lista) # [1, "Python", [40, 30, 20]] diff --git a/01 - Estrutura de dados/01 - Listas/10_count.py b/01 - Estrutura de dados/01 - Listas/10_count.py new file mode 100644 index 000000000..6bb8dffdd --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/10_count.py @@ -0,0 +1,5 @@ +cores = ["vermelho", "azul", "verde", "azul"] + +print(cores.count("vermelho")) # 1 +print(cores.count("azul")) # 2 +print(cores.count("verde")) # 1 diff --git a/01 - Estrutura de dados/01 - Listas/11_extend.py b/01 - Estrutura de dados/01 - Listas/11_extend.py new file mode 100644 index 000000000..b459aae75 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/11_extend.py @@ -0,0 +1,7 @@ +linguagens = ["python", "js", "c"] + +print(linguagens) # ["python", "js", "c"] + +linguagens.extend(["java", "csharp"]) + +print(linguagens) # ["python", "js", "c", "java", "csharp"] diff --git a/01 - Estrutura de dados/01 - Listas/12_index.py b/01 - Estrutura de dados/01 - Listas/12_index.py new file mode 100644 index 000000000..6c86f8340 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/12_index.py @@ -0,0 +1,4 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +print(linguagens.index("java")) # 3 +print(linguagens.index("python")) # 0 diff --git a/01 - Estrutura de dados/01 - Listas/13_pop.py b/01 - Estrutura de dados/01 - Listas/13_pop.py new file mode 100644 index 000000000..47869f080 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/13_pop.py @@ -0,0 +1,6 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +print(linguagens.pop()) # csharp +print(linguagens.pop()) # java +print(linguagens.pop()) # c +print(linguagens.pop(0)) # python diff --git a/01 - Estrutura de dados/01 - Listas/14_remove.py b/01 - Estrutura de dados/01 - Listas/14_remove.py new file mode 100644 index 000000000..bda8deb45 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/14_remove.py @@ -0,0 +1,5 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +linguagens.remove("c") + +print(linguagens) # ["python", "js", "java", "csharp"] diff --git a/01 - Estrutura de dados/01 - Listas/15_reverse.py b/01 - Estrutura de dados/01 - Listas/15_reverse.py new file mode 100644 index 000000000..cdf37116f --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/15_reverse.py @@ -0,0 +1,5 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +linguagens.reverse() + +print(linguagens) # ["csharp", "java", "c", "js", "python"] diff --git a/01 - Estrutura de dados/01 - Listas/16_sort.py b/01 - Estrutura de dados/01 - Listas/16_sort.py new file mode 100644 index 000000000..d4f81a160 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/16_sort.py @@ -0,0 +1,15 @@ +linguagens = ["python", "js", "c", "java", "csharp"] +linguagens.sort() # ["c", "csharp", "java", "js", "python"] +print(linguagens) + +linguagens = ["python", "js", "c", "java", "csharp"] +linguagens.sort(reverse=True) # ["python", "js", "java", "csharp", "c"] +print(linguagens) + +linguagens = ["python", "js", "c", "java", "csharp"] +linguagens.sort(key=lambda x: len(x)) # ["c", "js", "java", "python", "csharp"] +print(linguagens) + +linguagens = ["python", "js", "c", "java", "csharp"] +linguagens.sort(key=lambda x: len(x), reverse=True) # ["python", "csharp", "java", "js", "c"] +print(linguagens) diff --git a/01 - Estrutura de dados/01 - Listas/17_len.py b/01 - Estrutura de dados/01 - Listas/17_len.py new file mode 100644 index 000000000..867bef141 --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/17_len.py @@ -0,0 +1,3 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +print(len(linguagens)) # 5 diff --git a/01 - Estrutura de dados/01 - Listas/18_sorted.py b/01 - Estrutura de dados/01 - Listas/18_sorted.py new file mode 100644 index 000000000..78e68e48e --- /dev/null +++ b/01 - Estrutura de dados/01 - Listas/18_sorted.py @@ -0,0 +1,4 @@ +linguagens = ["python", "js", "c", "java", "csharp"] + +print(sorted(linguagens, key=lambda x: len(x))) # ["c", "js", "java", "python", "csharp"] +print(sorted(linguagens, key=lambda x: len(x), reverse=True)) # ["python", "csharp", "java", "js", "c"] diff --git a/01 - Estrutura de dados/02 - Tuplas/00_declarando_tuplas.py b/01 - Estrutura de dados/02 - Tuplas/00_declarando_tuplas.py new file mode 100644 index 000000000..f8ef748da --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/00_declarando_tuplas.py @@ -0,0 +1,15 @@ +frutas = ( + "laranja", + "pera", + "uva", +) +print(frutas) + +letras = tuple("python") +print(letras) + +numeros = tuple([1, 2, 3, 4]) +print(numeros) + +pais = ("Brasil",) +print(pais) diff --git a/01 - Estrutura de dados/02 - Tuplas/01_acesso_direto.py b/01 - Estrutura de dados/02 - Tuplas/01_acesso_direto.py new file mode 100644 index 000000000..c9c78ae2f --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/01_acesso_direto.py @@ -0,0 +1,4 @@ +frutas = ("maçã", "laranja", "uva", "pera",) + +print(frutas[0]) # maçã +print(frutas[2]) # uva diff --git a/01 - Estrutura de dados/02 - Tuplas/03_indices_negativos.py b/01 - Estrutura de dados/02 - Tuplas/03_indices_negativos.py new file mode 100644 index 000000000..692132aa6 --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/03_indices_negativos.py @@ -0,0 +1,9 @@ +frutas = ( + "maçã", + "laranja", + "uva", + "pera", +) + +print(frutas[-1]) # pera +print(frutas[-3]) # laranja diff --git a/01 - Estrutura de dados/02 - Tuplas/04_matriz.py b/01 - Estrutura de dados/02 - Tuplas/04_matriz.py new file mode 100644 index 000000000..53966832b --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/04_matriz.py @@ -0,0 +1,10 @@ +matriz = ( + (1, "a", 2), + ("b", 3, 4), + (6, 5, "c"), +) + +print(matriz[0]) # (1, "a", 2) +print(matriz[0][0]) # 1 +print(matriz[0][-1]) # 2 +print(matriz[-1][-1]) # "c" diff --git a/01 - Estrutura de dados/02 - Tuplas/05_fatiamento.py b/01 - Estrutura de dados/02 - Tuplas/05_fatiamento.py new file mode 100644 index 000000000..96cda10fc --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/05_fatiamento.py @@ -0,0 +1,8 @@ +tupla = ("p", "y", "t", "h", "o", "n",) + +print(tupla[2:]) # ("t", "h", "o", "n") +print(tupla[:2]) # ("p", "y") +print(tupla[1:3]) # ("y", "t") +print(tupla[0:3:2]) # ("p", "t") +print(tupla[::]) # ("p", "y", "t", "h", "o", "n") +print(tupla[::-1]) # ("n", "o", "h", "t", "y", "p") diff --git a/01 - Estrutura de dados/02 - Tuplas/06_iterar_tuplas.py b/01 - Estrutura de dados/02 - Tuplas/06_iterar_tuplas.py new file mode 100644 index 000000000..be263d11a --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/06_iterar_tuplas.py @@ -0,0 +1,12 @@ +carros = ( + "gol", + "celta", + "palio", +) + +for carro in carros: + print(carro) + + +for indice, carro in enumerate(carros): + print(f"{indice}: {carro}") diff --git a/01 - Estrutura de dados/02 - Tuplas/07_count.py b/01 - Estrutura de dados/02 - Tuplas/07_count.py new file mode 100644 index 000000000..54ba6390e --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/07_count.py @@ -0,0 +1,10 @@ +cores = ( + "vermelho", + "azul", + "verde", + "azul", +) + +print(cores.count("vermelho")) # 1 +print(cores.count("azul")) # 2 +print(cores.count("verde")) # 1 diff --git a/01 - Estrutura de dados/02 - Tuplas/08_index.py b/01 - Estrutura de dados/02 - Tuplas/08_index.py new file mode 100644 index 000000000..822916302 --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/08_index.py @@ -0,0 +1,4 @@ +linguagens = ("python", "js", "c", "java", "csharp",) + +print(linguagens.index("java")) # 3 +print(linguagens.index("python")) # 0 diff --git a/01 - Estrutura de dados/02 - Tuplas/09_len.py b/01 - Estrutura de dados/02 - Tuplas/09_len.py new file mode 100644 index 000000000..530bed576 --- /dev/null +++ b/01 - Estrutura de dados/02 - Tuplas/09_len.py @@ -0,0 +1,9 @@ +linguagens = ( + "python", + "js", + "c", + "java", + "csharp", +) + +print(len(linguagens)) # 5 diff --git a/01 - Estrutura de dados/03 - Conjuntos/00_declarando_conjuntos.py b/01 - Estrutura de dados/03 - Conjuntos/00_declarando_conjuntos.py new file mode 100644 index 000000000..1da3ba928 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/00_declarando_conjuntos.py @@ -0,0 +1,8 @@ +numeros = set([1, 2, 3, 1, 3, 4]) +print(numeros) # {1, 2, 3, 4} + +letras = set("abacaxi") +print(letras) # {"b", "a", "c", "x", "i"} + +carros = set(("palio", "gol", "celta", "palio")) +print(carros) # {"gol", "celta", "palio"} diff --git a/01 - Estrutura de dados/03 - Conjuntos/01_acessando_dados.py b/01 - Estrutura de dados/03 - Conjuntos/01_acessando_dados.py new file mode 100644 index 000000000..460ba674a --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/01_acessando_dados.py @@ -0,0 +1,5 @@ +numeros = {1, 2, 3, 2} + +numeros = list(numeros) + +print(numeros[0]) diff --git a/01 - Estrutura de dados/03 - Conjuntos/02_iterar_conjuntos.py b/01 - Estrutura de dados/03 - Conjuntos/02_iterar_conjuntos.py new file mode 100644 index 000000000..16e4ace63 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/02_iterar_conjuntos.py @@ -0,0 +1,7 @@ +carros = {"gol", "celta", "palio"} + +for carro in carros: + print(carro) + +for indice, carro in enumerate(carros): + print(f"{indice}: {carro}") diff --git a/01 - Estrutura de dados/03 - Conjuntos/03_union.py b/01 - Estrutura de dados/03 - Conjuntos/03_union.py new file mode 100644 index 000000000..54525e9ae --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/03_union.py @@ -0,0 +1,5 @@ +conjunto_a = {1, 2} +conjunto_b = {3, 4} + +resultado = conjunto_a.union(conjunto_b) +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/04_intersection.py b/01 - Estrutura de dados/03 - Conjuntos/04_intersection.py new file mode 100644 index 000000000..db3e9e653 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/04_intersection.py @@ -0,0 +1,5 @@ +conjunto_a = {1, 2, 3} +conjunto_b = {2, 3, 4} + +resultado = conjunto_a.intersection(conjunto_b) +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/05_difference.py b/01 - Estrutura de dados/03 - Conjuntos/05_difference.py new file mode 100644 index 000000000..b8c5ff40c --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/05_difference.py @@ -0,0 +1,8 @@ +conjunto_a = {1, 2, 3} +conjunto_b = {2, 3, 4} + +resultado = conjunto_a.difference(conjunto_b) +print(resultado) + +resultado = conjunto_b.difference(conjunto_a) +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/06_symmetric_difference.py b/01 - Estrutura de dados/03 - Conjuntos/06_symmetric_difference.py new file mode 100644 index 000000000..0acd3d087 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/06_symmetric_difference.py @@ -0,0 +1,5 @@ +conjunto_a = {1, 2, 3} +conjunto_b = {2, 3, 4} + +resultado = conjunto_a.symmetric_difference(conjunto_b) +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/07_issubset.py b/01 - Estrutura de dados/03 - Conjuntos/07_issubset.py new file mode 100644 index 000000000..7d6920bfb --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/07_issubset.py @@ -0,0 +1,8 @@ +conjunto_a = {1, 2, 3} +conjunto_b = {4, 1, 2, 5, 6, 3} + +resultado = conjunto_a.issubset(conjunto_b) # True +print(resultado) + +resultado = conjunto_b.issubset(conjunto_a) # False +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/08_issuperset.py b/01 - Estrutura de dados/03 - Conjuntos/08_issuperset.py new file mode 100644 index 000000000..bbdc795f0 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/08_issuperset.py @@ -0,0 +1,8 @@ +conjunto_a = {1, 2, 3} +conjunto_b = {4, 1, 2, 5, 6, 3} + +resultado = conjunto_a.issuperset(conjunto_b) # False +print(resultado) + +resultado = conjunto_b.issuperset(conjunto_a) # True +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/09_isdisjoint.py b/01 - Estrutura de dados/03 - Conjuntos/09_isdisjoint.py new file mode 100644 index 000000000..f54a1f0cf --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/09_isdisjoint.py @@ -0,0 +1,9 @@ +conjunto_a = {1, 2, 3, 4, 5} +conjunto_b = {6, 7, 8, 9} +conjunto_c = {1, 0} + +resultado = conjunto_a.isdisjoint(conjunto_b) # True +print(resultado) + +resultado = conjunto_a.isdisjoint(conjunto_c) # False +print(resultado) diff --git a/01 - Estrutura de dados/03 - Conjuntos/10_add.py b/01 - Estrutura de dados/03 - Conjuntos/10_add.py new file mode 100644 index 000000000..87c33e379 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/10_add.py @@ -0,0 +1,10 @@ +sorteio = {1, 23} + +sorteio.add(25) # {1, 23, 25} +print(sorteio) + +sorteio.add(42) # {1, 23, 25, 42} +print(sorteio) + +sorteio.add(25) # {1, 23, 25, 42} +print(sorteio) diff --git a/01 - Estrutura de dados/03 - Conjuntos/11_clear.py b/01 - Estrutura de dados/03 - Conjuntos/11_clear.py new file mode 100644 index 000000000..00fa3c77d --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/11_clear.py @@ -0,0 +1,7 @@ +sorteio = {1, 23} + +print(sorteio) # {1,23} + +sorteio.clear() + +print(sorteio) # {} diff --git a/01 - Estrutura de dados/03 - Conjuntos/12_copy.py b/01 - Estrutura de dados/03 - Conjuntos/12_copy.py new file mode 100644 index 000000000..40503a938 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/12_copy.py @@ -0,0 +1,7 @@ +sorteio = {1, 23} + +print(sorteio) # {1, 23} + +sorteio.copy() + +print(sorteio) # {1, 23} diff --git a/01 - Estrutura de dados/03 - Conjuntos/13_discard.py b/01 - Estrutura de dados/03 - Conjuntos/13_discard.py new file mode 100644 index 000000000..7eae807de --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/13_discard.py @@ -0,0 +1,8 @@ +numeros = {1, 2, 3, 1, 2, 4, 5, 5, 6, 7, 8, 9, 0} + +print(numeros) # {1, 2, 3, 4, 5, 6, 7, 8, 9, 0} + +numeros.discard(1) +numeros.discard(45) + +print(numeros) # {2, 3, 4, 5, 6, 7, 8, 9, 0} diff --git a/01 - Estrutura de dados/03 - Conjuntos/14_pop.py b/01 - Estrutura de dados/03 - Conjuntos/14_pop.py new file mode 100644 index 000000000..318d855bd --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/14_pop.py @@ -0,0 +1,6 @@ +numeros = {1, 2, 3, 1, 2, 4, 5, 5, 6, 7, 8, 9, 0} + +print(numeros) # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +print(numeros.pop()) # 0 +print(numeros.pop()) # 1 +print(numeros) # {2, 3, 4, 5, 6, 7, 8, 9} diff --git a/01 - Estrutura de dados/03 - Conjuntos/15_remove.py b/01 - Estrutura de dados/03 - Conjuntos/15_remove.py new file mode 100644 index 000000000..f887d80d0 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/15_remove.py @@ -0,0 +1,5 @@ +numeros = {1, 2, 3, 1, 2, 4, 5, 5, 6, 7, 8, 9, 0} + +print(numeros) # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +print(numeros.remove(0)) # 0 +print(numeros) # {1, 2, 3, 4, 5, 6, 7, 8, 9} diff --git a/01 - Estrutura de dados/03 - Conjuntos/16_len.py b/01 - Estrutura de dados/03 - Conjuntos/16_len.py new file mode 100644 index 000000000..c4bbf2e2c --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/16_len.py @@ -0,0 +1,3 @@ +numeros = {1, 2, 3, 1, 2, 4, 5, 5, 6, 7, 8, 9, 0} + +print(len(numeros)) # 10 diff --git a/01 - Estrutura de dados/03 - Conjuntos/17_in.py b/01 - Estrutura de dados/03 - Conjuntos/17_in.py new file mode 100644 index 000000000..f0080a362 --- /dev/null +++ b/01 - Estrutura de dados/03 - Conjuntos/17_in.py @@ -0,0 +1,4 @@ +numeros = {1, 2, 3, 1, 2, 4, 5, 5, 6, 7, 8, 9, 0} + +print(1 in numeros) # True +print(10 in numeros) # False diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/00_declarando_dicionarios.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/00_declarando_dicionarios.py" new file mode 100644 index 000000000..66ade22f6 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/00_declarando_dicionarios.py" @@ -0,0 +1,8 @@ +pessoa = {"nome": "Guilherme", "idade": 28} +print(pessoa) + +pessoa = dict(nome="Guilherme", idade=28) +print(pessoa) + +pessoa["telefone"] = "3333-1234" # {"nome": "Guilherme", "idade": 28, "telefone": "3333-1234"} +print(pessoa) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/01_acessando_dados.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/01_acessando_dados.py" new file mode 100644 index 000000000..63e023935 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/01_acessando_dados.py" @@ -0,0 +1,11 @@ +dados = {"nome": "Guilherme", "idade": 28, "telefone": "3333-1234"} + +print(dados["nome"]) # "Guilherme" +print(dados["idade"]) # 28 +print(dados["telefone"]) # "3333-1234" + +dados["nome"] = "Maria" +dados["idade"] = 18 +dados["telefone"] = "9988-1781" + +print(dados) # {"nome": "Maria", "idade": 18, "telefone": "9988-1781"} diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/02_dicionarios_aninhados.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/02_dicionarios_aninhados.py" new file mode 100644 index 000000000..f661d7abb --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/02_dicionarios_aninhados.py" @@ -0,0 +1,9 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +telefone = contatos["giovanna@gmail.com"]["telefone"] # "3443-2121" +print(telefone) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/03_iterando_dicionarios.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/03_iterando_dicionarios.py" new file mode 100644 index 000000000..2855a3808 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/03_iterando_dicionarios.py" @@ -0,0 +1,14 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +for chave in contatos: + print(chave, contatos[chave]) + +print("=" * 100) + +for chave, valor in contatos.items(): + print(chave, valor) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/04_clear.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/04_clear.py" new file mode 100644 index 000000000..281657f3d --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/04_clear.py" @@ -0,0 +1,9 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +contatos.clear() +print(contatos) # {} diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/05_copy.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/05_copy.py" new file mode 100644 index 000000000..0cb789041 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/05_copy.py" @@ -0,0 +1,8 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +copia = contatos.copy() +copia["guilherme@gmail.com"] = {"nome": "Gui"} + +print(contatos["guilherme@gmail.com"]) # {"nome": "Guilherme", "telefone": "3333-2221"} + +print(copia["guilherme@gmail.com"]) # {"nome": "Gui"} diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/06_fromkeys.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/06_fromkeys.py" new file mode 100644 index 000000000..2e3ee2ec1 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/06_fromkeys.py" @@ -0,0 +1,5 @@ +resultado = dict.fromkeys(["nome", "telefone"]) # {"nome": None, "telefone": None} +print(resultado) + +resultado = dict.fromkeys(["nome", "telefone"], "vazio") # {"nome": "vazio", "telefone": "vazio"} +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/07_get.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/07_get.py" new file mode 100644 index 000000000..11743aeed --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/07_get.py" @@ -0,0 +1,14 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +# contatos["chave"] # KeyError + +resultado = contatos.get("chave") # None +print(resultado) + +resultado = contatos.get("chave", {}) # {} +print(resultado) + +resultado = contatos.get( + "guilherme@gmail.com", {} +) # {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"} +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/08_items.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/08_items.py" new file mode 100644 index 000000000..b900765d4 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/08_items.py" @@ -0,0 +1,4 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +resultado = contatos.items() # dict_items([('guilherme@gmail.com', {'nome': 'Guilherme', 'telefone': '3333-2221'})]) +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/09_keys.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/09_keys.py" new file mode 100644 index 000000000..30a21ee79 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/09_keys.py" @@ -0,0 +1,4 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +resultado = contatos.keys() # dict_keys(['guilherme@gmail.com']) +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/10_pop.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/10_pop.py" new file mode 100644 index 000000000..b1784c9fb --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/10_pop.py" @@ -0,0 +1,7 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +resultado = contatos.pop("guilherme@gmail.com") # {'nome': 'Guilherme', 'telefone': '3333-2221'} +print(resultado) + +resultado = contatos.pop("guilherme@gmail.com", {}) # {} +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/11_popitem.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/11_popitem.py" new file mode 100644 index 000000000..e5ffa5681 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/11_popitem.py" @@ -0,0 +1,6 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +resultado = contatos.popitem() # ('guilherme@gmail.com', {'nome': 'Guilherme', 'telefone': '3333-2221'}) +print(resultado) + +# contatos.popitem() # KeyError diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/12_setdefault.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/12_setdefault.py" new file mode 100644 index 000000000..0b2a04481 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/12_setdefault.py" @@ -0,0 +1,7 @@ +contato = {"nome": "Guilherme", "telefone": "3333-2221"} + +contato.setdefault("nome", "Giovanna") # "Guilherme" +print(contato) # {'nome': 'Guilherme', 'telefone': '3333-2221'} + +contato.setdefault("idade", 28) # 28 +print(contato) # {'nome': 'Guilherme', 'telefone': '3333-2221', 'idade': 28} diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/13_update.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/13_update.py" new file mode 100644 index 000000000..6a9d7ec49 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/13_update.py" @@ -0,0 +1,8 @@ +contatos = {"guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}} + +contatos.update({"guilherme@gmail.com": {"nome": "Gui"}}) +print(contatos) # {'guilherme@gmail.com': {'nome': 'Gui'}} + +contatos.update({"giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3322-8181"}}) +# {'guilherme@gmail.com': {'nome': 'Gui'}, 'giovanna@gmail.com': {'nome': 'Giovanna', 'telefone': '3322-8181'}} +print(contatos) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/14_values.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/14_values.py" new file mode 100644 index 000000000..5535fc112 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/14_values.py" @@ -0,0 +1,11 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +resultado = ( + contatos.values() +) # dict_values([{'nome': 'Guilherme', 'telefone': '3333-2221'}, {'nome': 'Giovanna', 'telefone': '3443-2121'}, {'nome': 'Chappie', 'telefone': '3344-9871'}, {'nome': 'Melaine', 'telefone': '3333-7766'}]) # noqa +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/15_in.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/15_in.py" new file mode 100644 index 000000000..74dcd6cb1 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/15_in.py" @@ -0,0 +1,18 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +resultado = "guilherme@gmail.com" in contatos # True +print(resultado) + +resultado = "megui@gmail.com" in contatos # False +print(resultado) + +resultado = "idade" in contatos["guilherme@gmail.com"] # False +print(resultado) + +resultado = "telefone" in contatos["giovanna@gmail.com"] # True +print(resultado) diff --git "a/01 - Estrutura de dados/04 - Dicion\303\241rios/16_del.py" "b/01 - Estrutura de dados/04 - Dicion\303\241rios/16_del.py" new file mode 100644 index 000000000..565fc2460 --- /dev/null +++ "b/01 - Estrutura de dados/04 - Dicion\303\241rios/16_del.py" @@ -0,0 +1,12 @@ +contatos = { + "guilherme@gmail.com": {"nome": "Guilherme", "telefone": "3333-2221"}, + "giovanna@gmail.com": {"nome": "Giovanna", "telefone": "3443-2121"}, + "chappie@gmail.com": {"nome": "Chappie", "telefone": "3344-9871"}, + "melaine@gmail.com": {"nome": "Melaine", "telefone": "3333-7766"}, +} + +del contatos["guilherme@gmail.com"]["telefone"] +del contatos["chappie@gmail.com"] + +# {'guilherme@gmail.com': {'nome': 'Guilherme'}, 'giovanna@gmail.com': {'nome': 'Giovanna', 'telefone': '3443-2121'}, 'melaine@gmail.com': {'nome': 'Melaine', 'telefone': '3333-7766'}} # noqa +print(contatos) diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/00_primeira_funcao.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/00_primeira_funcao.py" new file mode 100644 index 000000000..b966e7789 --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/00_primeira_funcao.py" @@ -0,0 +1,16 @@ +def exibir_mensagem(): + print("Olá mundo!") + + +def exibir_mensagem_2(nome): + print(f"Seja bem vindo {nome}!") + + +def exibir_mensagem_3(nome="Anônimo"): + print(f"Seja bem vindo {nome}!") + + +exibir_mensagem() +exibir_mensagem_2(nome="Guilherme") +exibir_mensagem_3() +exibir_mensagem_3(nome="Chappie") diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/01_retorno_da_funcao.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/01_retorno_da_funcao.py" new file mode 100644 index 000000000..c7fb23a62 --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/01_retorno_da_funcao.py" @@ -0,0 +1,13 @@ +def calcular_total(numeros): + return sum(numeros) + + +def retorna_antecessor_e_sucessor(numero): + antecessor = numero - 1 + sucessor = numero + 1 + + return antecessor, sucessor + + +print(calcular_total([10, 20, 34])) # 64 +print(retorna_antecessor_e_sucessor(10)) # (9, 11) diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/02_argumentos_nomeados.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/02_argumentos_nomeados.py" new file mode 100644 index 000000000..3c1c28021 --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/02_argumentos_nomeados.py" @@ -0,0 +1,8 @@ +def salvar_carro(marca, modelo, ano, placa): + # salva carro no banco de dados... + print(f"Carro inserido com sucesso! {marca}/{modelo}/{ano}/{placa}") + + +salvar_carro("Fiat", "Palio", 1999, "ABC-1234") +salvar_carro(marca="Fiat", modelo="Palio", ano=1999, placa="ABC-1234") +salvar_carro(**{"marca": "Fiat", "modelo": "Palio", "ano": 1999, "placa": "ABC-1234"}) diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/03_args_kwargs.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/03_args_kwargs.py" new file mode 100644 index 000000000..9589d5e62 --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/03_args_kwargs.py" @@ -0,0 +1,31 @@ +def exibir_poema(data_extenso, *args, **kwargs): + texto = "\n".join(args) + meta_dados = "\n".join([f"{chave.title()}: {valor}" for chave, valor in kwargs.items()]) + mensagem = f"{data_extenso}\n\n{texto}\n\n{meta_dados}" + print(mensagem) + + +exibir_poema( + "Zen of Python", + "Beautiful is better than ugly.", + "Explicit is better than implicit.", + "Simple is better than complex.", + "Complex is better than complicated.", + "Flat is better than nested.", + "Sparse is better than dense.", + "Readability counts.", + "Special cases aren't special enough to break the rules.", + "Although practicality beats purity.", + "Errors should never pass silently.", + "Unless explicitly silenced.", + "In the face of ambiguity, refuse the temptation to guess.", + "There should be one-- and preferably only one --obvious way to do it.", + "Although that way may not be obvious at first unless you're Dutch.", + "Now is better than never.", + "Although never is often better than *right* now.", + "If the implementation is hard to explain, it's a bad idea.", + "If the implementation is easy to explain, it may be a good idea.", + "Namespaces are one honking great idea -- let's do more of those!", + autor="Tim Peters", + ano=1999, +) diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/04_parametros_somente_por_posicao copy.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/04_parametros_somente_por_posicao copy.py" new file mode 100644 index 000000000..966d9398d --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/04_parametros_somente_por_posicao copy.py" @@ -0,0 +1,6 @@ +def criar_carro(modelo, ano, placa, /, marca, motor, combustivel): + print(modelo, ano, placa, marca, motor, combustivel) + + +criar_carro("Palio", 1999, "ABC-1234", marca="Fiat", motor="1.0", combustivel="Gasolina") +criar_carro(modelo="Palio", ano=1999, placa="ABC-1234", marca="Fiat", motor="1.0", combustivel="Gasolina") # inválido diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/05_parametros_somente_por_nome.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/05_parametros_somente_por_nome.py" new file mode 100644 index 000000000..5dfc7d93f --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/05_parametros_somente_por_nome.py" @@ -0,0 +1,6 @@ +def criar_carro(modelo, ano, placa, /, *, marca, motor, combustivel): + print(modelo, ano, placa, marca, motor, combustivel) + + +# criar_carro("Palio", 1999, "ABC-1234", marca="Fiat", motor="1.0", combustivel="Gasolina") +criar_carro(modelo="Palio", ano=1999, placa="ABC-1234", marca="Fiat", motor="1.0", combustivel="Gasolina") # inválido diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/06_objetos_de_primeira_classe.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/06_objetos_de_primeira_classe.py" new file mode 100644 index 000000000..2b18dda94 --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/06_objetos_de_primeira_classe.py" @@ -0,0 +1,10 @@ +def somar(a, b): + return a + b + + +def exibir_resultado(a, b, funcao): + resultado = funcao(a, b) + print(f"O resultado da operação {a} + {b} = {resultado}") + + +exibir_resultado(10, 10, somar) # O resultado da operação 10 + 10 = 20 diff --git "a/01 - Estrutura de dados/05 - Fun\303\247\303\265es/07_escopo_local_e_global.py" "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/07_escopo_local_e_global.py" new file mode 100644 index 000000000..d158815ed --- /dev/null +++ "b/01 - Estrutura de dados/05 - Fun\303\247\303\265es/07_escopo_local_e_global.py" @@ -0,0 +1,10 @@ +salario = 2000 + + +def salario_bonus(bonus): + global salario + salario += bonus + return salario + + +salario_bonus(500) # 2500 From ae40f5eafa4e3c3384e066cfa19471712c44b0de Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Sun, 11 Sep 2022 02:28:16 -0300 Subject: [PATCH 03/16] feat: challenge resolution --- 01 - Estrutura de dados/desafio.py | 160 +++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 01 - Estrutura de dados/desafio.py diff --git a/01 - Estrutura de dados/desafio.py b/01 - Estrutura de dados/desafio.py new file mode 100644 index 000000000..2ce9ef270 --- /dev/null +++ b/01 - Estrutura de dados/desafio.py @@ -0,0 +1,160 @@ +import textwrap + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def depositar(saldo, valor, extrato, /): + if valor > 0: + saldo += valor + extrato += f"Depósito:\tR$ {valor:.2f}\n" + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return saldo, extrato + + +def sacar(*, saldo, valor, extrato, limite, numero_saques, limite_saques): + excedeu_saldo = valor > saldo + excedeu_limite = valor > limite + excedeu_saques = numero_saques >= limite_saques + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + elif valor > 0: + saldo -= valor + extrato += f"Saque:\t\tR$ {valor:.2f}\n" + numero_saques += 1 + print("\n=== Saque realizado com sucesso! ===") + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return saldo, extrato + + +def exibir_extrato(saldo, /, *, extrato): + print("\n================ EXTRATO ================") + print("Não foram realizadas movimentações." if not extrato else extrato) + print(f"\nSaldo:\t\tR$ {saldo:.2f}") + print("==========================================") + + +def criar_usuario(usuarios): + cpf = input("Informe o CPF (somente número): ") + usuario = filtrar_usuario(cpf, usuarios) + + if usuario: + print("\n@@@ Já existe usuário com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + usuarios.append({"nome": nome, "data_nascimento": data_nascimento, "cpf": cpf, "endereco": endereco}) + + print("=== Usuário criado com sucesso! ===") + + +def filtrar_usuario(cpf, usuarios): + usuarios_filtrados = [usuario for usuario in usuarios if usuario["cpf"] == cpf] + return usuarios_filtrados[0] if usuarios_filtrados else None + + +def criar_conta(agencia, numero_conta, usuarios): + cpf = input("Informe o CPF do usuário: ") + usuario = filtrar_usuario(cpf, usuarios) + + if usuario: + print("\n=== Conta criada com sucesso! ===") + return {"agencia": agencia, "numero_conta": numero_conta, "usuario": usuario} + + print("\n@@@ Usuário não encontrado, fluxo de criação de conta encerrado! @@@") + + +def listar_contas(contas): + for conta in contas: + linha = f"""\ + Agência:\t{conta['agencia']} + C/C:\t\t{conta['numero_conta']} + Titular:\t{conta['usuario']['nome']} + """ + print("=" * 100) + print(textwrap.dedent(linha)) + + +def main(): + LIMITE_SAQUES = 3 + AGENCIA = "0001" + + saldo = 0 + limite = 500 + extrato = "" + numero_saques = 0 + usuarios = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + valor = float(input("Informe o valor do depósito: ")) + + saldo, extrato = depositar(saldo, valor, extrato) + + elif opcao == "s": + valor = float(input("Informe o valor do saque: ")) + + saldo, extrato = sacar( + saldo=saldo, + valor=valor, + extrato=extrato, + limite=limite, + numero_saques=numero_saques, + limite_saques=LIMITE_SAQUES, + ) + + elif opcao == "e": + exibir_extrato(saldo, extrato=extrato) + + elif opcao == "nu": + criar_usuario(usuarios) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + conta = criar_conta(AGENCIA, numero_conta, usuarios) + + if conta: + contas.append(conta) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("Operação inválida, por favor selecione novamente a operação desejada.") + + +main() From 39f5a6601989fdb74bf43424a6c79c189115370a Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 26 Jul 2023 02:52:21 -0300 Subject: [PATCH 04/16] feat: add code of date and hour lessons --- 04 - Data e hora/1_datetime.py | 13 +++++++++++++ 04 - Data e hora/2_timedelta.py | 25 +++++++++++++++++++++++++ 04 - Data e hora/3_strftime_strptime.py | 12 ++++++++++++ 04 - Data e hora/4_pytz.py | 9 +++++++++ 04 - Data e hora/5_timezone.py | 7 +++++++ 5 files changed, 66 insertions(+) create mode 100644 04 - Data e hora/1_datetime.py create mode 100644 04 - Data e hora/2_timedelta.py create mode 100644 04 - Data e hora/3_strftime_strptime.py create mode 100644 04 - Data e hora/4_pytz.py create mode 100644 04 - Data e hora/5_timezone.py diff --git a/04 - Data e hora/1_datetime.py b/04 - Data e hora/1_datetime.py new file mode 100644 index 000000000..7de91d36e --- /dev/null +++ b/04 - Data e hora/1_datetime.py @@ -0,0 +1,13 @@ +from datetime import date, datetime, time + +data = date(2023, 7, 10) +print(data) +print(date.today()) + + +data_hora = datetime(2023, 7, 10) +print(data_hora) +print(datetime.today()) + +hora = time(10, 20, 0) +print(hora) diff --git a/04 - Data e hora/2_timedelta.py b/04 - Data e hora/2_timedelta.py new file mode 100644 index 000000000..449ca1b9c --- /dev/null +++ b/04 - Data e hora/2_timedelta.py @@ -0,0 +1,25 @@ +from datetime import date, datetime, timedelta + +tipo_carro = "M" # P, M, G +tempo_pequeno = 30 +tempo_medio = 45 +tempo_grande = 60 +data_atual = datetime.now() + +if tipo_carro == "P": + data_estimada = data_atual - timedelta(days=tempo_pequeno) + print(f"O carro chegou: {data_atual} e ficará pronto às {data_estimada}") +elif tipo_carro == "M": + data_estimada = data_atual - timedelta(days=tempo_medio) + print(f"O carro chegou: {data_atual} e ficará pronto às {data_estimada}") +else: + data_estimada = data_atual - timedelta(days=tempo_grande) + print(f"O carro chegou: {data_atual} e ficará pronto às {data_estimada}") + + +print(date.today() - timedelta(days=1)) + +resultado = datetime(2023, 7, 25, 10, 19, 20) - timedelta(hours=1) +print(resultado.time()) + +print(datetime.now().date()) diff --git a/04 - Data e hora/3_strftime_strptime.py b/04 - Data e hora/3_strftime_strptime.py new file mode 100644 index 000000000..e3cb1a2ce --- /dev/null +++ b/04 - Data e hora/3_strftime_strptime.py @@ -0,0 +1,12 @@ +from datetime import datetime + +data_hora_atual = datetime.now() +data_hora_str = "2023-10-20 10:20" +mascara_ptbr = "%d/%m/%Y %a" +mascara_en = "%Y-%m-%d %H:%M" + +print(data_hora_atual.strftime(mascara_ptbr)) + +data_convertida = datetime.strptime(data_hora_str, mascara_en) +print(data_convertida) +print(type(data_convertida)) diff --git a/04 - Data e hora/4_pytz.py b/04 - Data e hora/4_pytz.py new file mode 100644 index 000000000..ada5569c6 --- /dev/null +++ b/04 - Data e hora/4_pytz.py @@ -0,0 +1,9 @@ +from datetime import datetime + +import pytz + +data = datetime.now(pytz.timezone("Europe/Oslo")) +data2 = datetime.now(pytz.timezone("America/Sao_Paulo")) + +print(data) +print(data2) diff --git a/04 - Data e hora/5_timezone.py b/04 - Data e hora/5_timezone.py new file mode 100644 index 000000000..dd765cb05 --- /dev/null +++ b/04 - Data e hora/5_timezone.py @@ -0,0 +1,7 @@ +from datetime import datetime, timedelta, timezone + +data_oslo = datetime.now(timezone(timedelta(hours=2))) +data_sao_paulo = datetime.now(timezone(timedelta(hours=-3))) + +print(data_oslo) +print(data_sao_paulo) From efa05e3893ab13c707a4d9b96e5ad778884660bf Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 17 Jan 2024 12:38:55 -0300 Subject: [PATCH 05/16] feat: add module 3 files --- .../1_passagem_param.py | 17 + .../2_funcao_interna.py | 14 + .../3_retorna_funcao.py | 32 ++ .../4.1_primeiro_decorador_acucar_sintax.py | 15 + .../4_primeiro_decorador.py | 15 + .../5.1_decorador_introspeccao.py | 17 + ...1_decorador_retorna_valor_func_decorada.py | 15 + .../5_decorador_com_argumentos copy.py | 19 + .../6_iteradores.py | 19 + .../7_geradores.py | 7 + .../desafio/desafio_v1.py | 367 +++++++++++++++++ .../desafio/desafio_v2.py | 385 ++++++++++++++++++ 12 files changed, 922 insertions(+) create mode 100644 03 - Decoradores, Iteradores e Geradores/1_passagem_param.py create mode 100644 03 - Decoradores, Iteradores e Geradores/2_funcao_interna.py create mode 100644 03 - Decoradores, Iteradores e Geradores/3_retorna_funcao.py create mode 100644 03 - Decoradores, Iteradores e Geradores/4.1_primeiro_decorador_acucar_sintax.py create mode 100644 03 - Decoradores, Iteradores e Geradores/4_primeiro_decorador.py create mode 100644 03 - Decoradores, Iteradores e Geradores/5.1_decorador_introspeccao.py create mode 100644 03 - Decoradores, Iteradores e Geradores/5.1_decorador_retorna_valor_func_decorada.py create mode 100644 03 - Decoradores, Iteradores e Geradores/5_decorador_com_argumentos copy.py create mode 100644 03 - Decoradores, Iteradores e Geradores/6_iteradores.py create mode 100644 03 - Decoradores, Iteradores e Geradores/7_geradores.py create mode 100644 03 - Decoradores, Iteradores e Geradores/desafio/desafio_v1.py create mode 100644 03 - Decoradores, Iteradores e Geradores/desafio/desafio_v2.py diff --git a/03 - Decoradores, Iteradores e Geradores/1_passagem_param.py b/03 - Decoradores, Iteradores e Geradores/1_passagem_param.py new file mode 100644 index 000000000..a2ce6e89f --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/1_passagem_param.py @@ -0,0 +1,17 @@ +def mensagem(nome): + print("executando mensagem") + return f"Oi {nome}" + + +def mensagem_longa(nome): + print("executando mensagem longa") + return f"Olá tudo bem com você {nome}?" + + +def executar(funcao, nome): + print("executando executar") + return funcao(nome) + + +print(executar(mensagem, "Joao")) +print(executar(mensagem_longa, "Joao")) diff --git a/03 - Decoradores, Iteradores e Geradores/2_funcao_interna.py b/03 - Decoradores, Iteradores e Geradores/2_funcao_interna.py new file mode 100644 index 000000000..047df0daf --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/2_funcao_interna.py @@ -0,0 +1,14 @@ +def principal(): + print("executando a funcao principal") + + def funcao_interna(): + print("executando a funcao interna") + + def funcao_2(): + print("executando a funcao 2") + + funcao_interna() + funcao_2() + + +principal() diff --git a/03 - Decoradores, Iteradores e Geradores/3_retorna_funcao.py b/03 - Decoradores, Iteradores e Geradores/3_retorna_funcao.py new file mode 100644 index 000000000..1ab919feb --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/3_retorna_funcao.py @@ -0,0 +1,32 @@ +def calculadora(operacao): + def soma(a, b): + return a + b + + def sub(a, b): + return a - b + + def mul(a, b): + return a * b + + def div(a, b): + return a / b + + match operacao: + case "+": + return soma + case "-": + return sub + case "*": + return mul + case "/": + return div + + +op = calculadora("+") +print(op(2, 2)) +op = calculadora("-") +print(op(2, 2)) +op = calculadora("*") +print(op(2, 2)) +op = calculadora("/") +print(op(2, 2)) diff --git a/03 - Decoradores, Iteradores e Geradores/4.1_primeiro_decorador_acucar_sintax.py b/03 - Decoradores, Iteradores e Geradores/4.1_primeiro_decorador_acucar_sintax.py new file mode 100644 index 000000000..6d023af84 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/4.1_primeiro_decorador_acucar_sintax.py @@ -0,0 +1,15 @@ +def meu_decorador(funcao): + def envelope(): + print("faz algo antes de executar") + funcao() + print("faz algo depois de executar") + + return envelope + + +@meu_decorador +def ola_mundo(): + print("Olá mundo!") + + +ola_mundo() diff --git a/03 - Decoradores, Iteradores e Geradores/4_primeiro_decorador.py b/03 - Decoradores, Iteradores e Geradores/4_primeiro_decorador.py new file mode 100644 index 000000000..2bda154ff --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/4_primeiro_decorador.py @@ -0,0 +1,15 @@ +def meu_decorador(funcao): + def envelope(): + print("faz algo antes de executar") + funcao() + print("faz algo depois de executar") + + return envelope + + +def ola_mundo(): + print("Olá mundo!") + + +ola_mundo = meu_decorador(ola_mundo) +ola_mundo() diff --git a/03 - Decoradores, Iteradores e Geradores/5.1_decorador_introspeccao.py b/03 - Decoradores, Iteradores e Geradores/5.1_decorador_introspeccao.py new file mode 100644 index 000000000..8b93c7480 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/5.1_decorador_introspeccao.py @@ -0,0 +1,17 @@ +import functools + + +def meu_decorador(funcao): + @functools.wraps(funcao) + def envelope(*args, **kwargs): + funcao(*args, **kwargs) + + return envelope + + +@meu_decorador +def ola_mundo(nome, outro_argumento): + print(f"Olá mundo {nome}!") + + +print(ola_mundo.__name__) diff --git a/03 - Decoradores, Iteradores e Geradores/5.1_decorador_retorna_valor_func_decorada.py b/03 - Decoradores, Iteradores e Geradores/5.1_decorador_retorna_valor_func_decorada.py new file mode 100644 index 000000000..66b2931a8 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/5.1_decorador_retorna_valor_func_decorada.py @@ -0,0 +1,15 @@ +def meu_decorador(funcao): + def envelope(*args, **kwargs): + print("faz algo antes de executar") + funcao(*args, **kwargs) + print("faz algo depois de executar") + + return envelope + + +@meu_decorador +def ola_mundo(nome, outro_argumento): + print(f"Olá mundo {nome}!") + + +ola_mundo("João", 1000) diff --git a/03 - Decoradores, Iteradores e Geradores/5_decorador_com_argumentos copy.py b/03 - Decoradores, Iteradores e Geradores/5_decorador_com_argumentos copy.py new file mode 100644 index 000000000..df73306c7 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/5_decorador_com_argumentos copy.py @@ -0,0 +1,19 @@ +def meu_decorador(funcao): + def envelope(*args, **kwargs): + print("faz algo antes de executar") + resultado = funcao(*args, **kwargs) + print("faz algo depois de executar") + return resultado + + return envelope + + +@meu_decorador +def ola_mundo(nome, outro_argumento): + print(f"Olá mundo {nome}!") + return nome.upper() + + +resultado = ola_mundo("João", 1000) +print(resultado) +print(ola_mundo) diff --git a/03 - Decoradores, Iteradores e Geradores/6_iteradores.py b/03 - Decoradores, Iteradores e Geradores/6_iteradores.py new file mode 100644 index 000000000..b9954d7a0 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/6_iteradores.py @@ -0,0 +1,19 @@ +class MeuIterador: + def __init__(self, numeros: list[int]): + self.numeros = numeros + self.contador = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + numero = self.numeros[self.contador] + self.contador += 1 + return numero * 2 + except IndexError: + raise StopIteration + + +for i in MeuIterador(numeros=[38, 13, 11]): + print(i) diff --git a/03 - Decoradores, Iteradores e Geradores/7_geradores.py b/03 - Decoradores, Iteradores e Geradores/7_geradores.py new file mode 100644 index 000000000..d547b534c --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/7_geradores.py @@ -0,0 +1,7 @@ +def meu_gerador(numeros: list[int]): + for numero in numeros: + yield numero * 2 + + +for i in meu_gerador(numeros=[1, 2, 3]): + print(i) diff --git a/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v1.py b/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v1.py new file mode 100644 index 000000000..3f0cf4e7a --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v1.py @@ -0,0 +1,367 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContaIterador: + def __init__(self, contas): + pass + + def __iter__(self): + pass + + def __next__(self): + pass + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + + def realizar_transacao(self, conta, transacao): + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + pass + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + pass + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + # TODO: atualizar a implementação para utilizar o gerador definido em Historico + transacoes = conta.historico.transacoes + + extrato = "" + if not transacoes: + extrato = "Não foram realizadas movimentações." + else: + for transacao in transacoes: + extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + # TODO: alterar implementação, para utilizar a classe ContaIterador + for conta in contas: + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git a/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v2.py b/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v2.py new file mode 100644 index 000000000..9c2860706 --- /dev/null +++ b/03 - Decoradores, Iteradores e Geradores/desafio/desafio_v2.py @@ -0,0 +1,385 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + print(f"{datetime.now()}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(tipo_transacao="saque"): + tem_transacao = True + extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() From 8109002596cd189a7d438462552ed676b698c7ee Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 17 Jan 2024 12:39:18 -0300 Subject: [PATCH 06/16] feat: add module 4 files --- 04 - Data e hora/desafio/desafio_v1.py | 396 +++++++++++++++++++++++ 04 - Data e hora/desafio/desafio_v2.py | 419 +++++++++++++++++++++++++ 2 files changed, 815 insertions(+) create mode 100644 04 - Data e hora/desafio/desafio_v1.py create mode 100644 04 - Data e hora/desafio/desafio_v2.py diff --git a/04 - Data e hora/desafio/desafio_v1.py b/04 - Data e hora/desafio/desafio_v1.py new file mode 100644 index 000000000..1901800f6 --- /dev/null +++ b/04 - Data e hora/desafio/desafio_v1.py @@ -0,0 +1,396 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + # TODO: validar o número de transações e invalidar a operação se for necessário + # print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + # TODO: filtrar todas as transações realizadas no dia + def transacoes_do_dia(self): + pass + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + print(f"{datetime.now()}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + # NOTE: O valor padrão de limite de saques foi alterado para 50 saques + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta, limite=500, limite_saques=50) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git a/04 - Data e hora/desafio/desafio_v2.py b/04 - Data e hora/desafio/desafio_v2.py new file mode 100644 index 000000000..451173290 --- /dev/null +++ b/04 - Data e hora/desafio/desafio_v2.py @@ -0,0 +1,419 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + if len(conta.historico.transacoes_do_dia()) >= 2: + print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + return + + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [ + transacao + for transacao in self.historico.transacoes + if transacao["tipo"] == Saque.__name__ + ] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.utcnow().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if ( + tipo_transacao is None + or transacao["tipo"].lower() == tipo_transacao.lower() + ): + yield transacao + + def transacoes_do_dia(self): + data_atual = datetime.utcnow().date() + transacoes = [] + for transacao in self._transacoes: + data_transacao = datetime.strptime( + transacao["data"], "%d-%m-%Y %H:%M:%S" + ).date() + if data_atual == data_transacao: + transacoes.append(transacao) + return transacoes + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + print(f"{datetime.now()}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f"\n{transacao['data']}\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input( + "Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): " + ) + + cliente = PessoaFisica( + nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco + ) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + conta = ContaCorrente.nova_conta( + cliente=cliente, numero=numero_conta, limite=500, limite_saques=50 + ) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print( + "\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@" + ) + + +main() From fbc41cd1fdbabc13697542bd267e81a21f715333 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 17 Jan 2024 12:39:38 -0300 Subject: [PATCH 07/16] feat: add module 5 files --- .../1_operacao_leitura.py" | 28 ++ .../2_operacao_escrita.py" | 6 + .../3_os_shutil.py" | 16 + .../4_tratamento_erro.py" | 22 + .../5_boas_praticas.py" | 24 + .../6_csv.py" | 39 ++ .../arquivo-utf-8.txt" | 1 + .../desafio/desafio_v1.py" | 411 +++++++++++++++++ .../desafio/desafio_v2.py" | 416 ++++++++++++++++++ .../desafio/log.txt" | 6 + .../lorem.txt" | 2 + .../novo-diretorio/novo.txt" | 0 .../teste.txt" | 5 + .../usuarios.csv" | 3 + 14 files changed, 979 insertions(+) create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/1_operacao_leitura.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/2_operacao_escrita.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/3_os_shutil.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/4_tratamento_erro.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/5_boas_praticas.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/6_csv.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/arquivo-utf-8.txt" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v1.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v2.py" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/desafio/log.txt" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/lorem.txt" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/novo-diretorio/novo.txt" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/teste.txt" create mode 100644 "05 - Manipula\303\247\303\243o de arquivos/usuarios.csv" diff --git "a/05 - Manipula\303\247\303\243o de arquivos/1_operacao_leitura.py" "b/05 - Manipula\303\247\303\243o de arquivos/1_operacao_leitura.py" new file mode 100644 index 000000000..354ea98ee --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/1_operacao_leitura.py" @@ -0,0 +1,28 @@ +# Lembre-se de alterar o caminho do arquivo, para o caminho completo da sua máquina! + +arquivo = open( + "/home/guilherme/Projetos/dio/codigo-fonte/trilha-python-dio/05 - Manipulação de arquivos/lorem.txt", "r" +) +print(arquivo.read()) +arquivo.close() + +arquivo = open( + "/home/guilherme/Projetos/dio/codigo-fonte/trilha-python-dio/05 - Manipulação de arquivos/lorem.txt", "r" +) +print(arquivo.readline()) +arquivo.close() + +arquivo = open( + "/home/guilherme/Projetos/dio/codigo-fonte/trilha-python-dio/05 - Manipulação de arquivos/lorem.txt", "r" +) +print(arquivo.readlines()) +arquivo.close() + +arquivo = open( + "/home/guilherme/Projetos/dio/codigo-fonte/trilha-python-dio/05 - Manipulação de arquivos/lorem.txt", "r" +) +# tip +while len(linha := arquivo.readline()): + print(linha) + +arquivo.close() diff --git "a/05 - Manipula\303\247\303\243o de arquivos/2_operacao_escrita.py" "b/05 - Manipula\303\247\303\243o de arquivos/2_operacao_escrita.py" new file mode 100644 index 000000000..3543afbeb --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/2_operacao_escrita.py" @@ -0,0 +1,6 @@ +arquivo = open( + "/home/guilherme/Projetos/dio/codigo-fonte/trilha-python-dio/05 - Manipulação de arquivos/teste.txt", "w" +) +arquivo.write("Escrevendo dados em um novo arquivo.") +arquivo.writelines(["\n", "escrevendo", "\n", "um", "\n", "novo", "\n", "texto"]) +arquivo.close() diff --git "a/05 - Manipula\303\247\303\243o de arquivos/3_os_shutil.py" "b/05 - Manipula\303\247\303\243o de arquivos/3_os_shutil.py" new file mode 100644 index 000000000..aaa6280f5 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/3_os_shutil.py" @@ -0,0 +1,16 @@ +import os +import shutil +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +os.mkdir(ROOT_PATH / "novo-diretorio") + +arquivo = open(ROOT_PATH / "novo.txt", "w") +arquivo.close() + +os.rename(ROOT_PATH / "novo.txt", ROOT_PATH / "alterado.txt") + +os.remove(ROOT_PATH / "alterado.txt") + +shutil.move(ROOT_PATH / "novo.txt", ROOT_PATH / "novo-diretorio" / "novo.txt") diff --git "a/05 - Manipula\303\247\303\243o de arquivos/4_tratamento_erro.py" "b/05 - Manipula\303\247\303\243o de arquivos/4_tratamento_erro.py" new file mode 100644 index 000000000..fb099fb8f --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/4_tratamento_erro.py" @@ -0,0 +1,22 @@ +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + + +try: + arquivo = open(ROOT_PATH / "novo-diretorio" / "novo.txt", "r") +except FileNotFoundError as exc: + print("Arquivo não encontrado!") + print(exc) +except IsADirectoryError as exc: + print(f"Não foi possível abrir o arquivo: {exc}") +except IOError as exc: + print(f"Erro ao abrir o arquivo: {exc}") +except Exception as exc: + print(f"Algum problema ocorreu ao tentar abrir o arquivo: {exc}") + + +# try: +# arquivo = open(ROOT_PATH / "novo-diretorio") +# except IsADirectoryError as exc: +# print(f"Não foi possível abrir o arquivo: {exc}") diff --git "a/05 - Manipula\303\247\303\243o de arquivos/5_boas_praticas.py" "b/05 - Manipula\303\247\303\243o de arquivos/5_boas_praticas.py" new file mode 100644 index 000000000..eda7a6160 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/5_boas_praticas.py" @@ -0,0 +1,24 @@ +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +try: + with open(ROOT_PATH / "1lorem.txt", "r") as arquivo: + print(arquivo.read()) +except IOError as exc: + print(f"Erro ao abrir o arquivo {exc}") + + +# try: +# with open(ROOT_PATH / "arquivo-utf-8.txt", "w", encoding="utf-8") as arquivo: +# arquivo.write("Aprendendo a manipular arquivos utilizando Python.") +# except IOError as exc: +# print(f"Erro ao abrir o arquivo {exc}") + +try: + with open(ROOT_PATH / "arquivo-utf-8.txt", "r", encoding="utf-8") as arquivo: + print(arquivo.read()) +except IOError as exc: + print(f"Erro ao abrir o arquivo {exc}") +except UnicodeDecodeError as exc: + print(exc) diff --git "a/05 - Manipula\303\247\303\243o de arquivos/6_csv.py" "b/05 - Manipula\303\247\303\243o de arquivos/6_csv.py" new file mode 100644 index 000000000..04f536035 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/6_csv.py" @@ -0,0 +1,39 @@ +import csv +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +COLUNA_ID = 0 +COLUNA_NOME = 1 + + +try: + with open(ROOT_PATH / "usuarios.csv", "w", newline="", encoding="utf-8") as arquivo: + escritor = csv.writer(arquivo) + escritor.writerow(["id", "nome"]) + escritor.writerow(["1", "Maria"]) + escritor.writerow(["2", "João"]) +except IOError as exc: + print(f"Erro ao criar o arquivo. {exc}") + + +try: + with open(ROOT_PATH / "usuarios.csv", "r", newline="", encoding="utf-8") as arquivo: + leitor = csv.reader(arquivo) + for idx, row in enumerate(leitor): + if idx == 0: + continue + print(f"ID: {row[COLUNA_ID]}") + print(f"Nome: {row[COLUNA_NOME]}") +except IOError as exc: + print(f"Erro ao criar o arquivo. {exc}") + + +try: + with open(ROOT_PATH / "usuarios.csv", newline="") as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + print(f"ID: {row['id']}") + print(f"Nome: {row['nome']}") +except IOError as exc: + print(f"Erro ao criar o arquivo. {exc}") diff --git "a/05 - Manipula\303\247\303\243o de arquivos/arquivo-utf-8.txt" "b/05 - Manipula\303\247\303\243o de arquivos/arquivo-utf-8.txt" new file mode 100644 index 000000000..de149a6be --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/arquivo-utf-8.txt" @@ -0,0 +1 @@ +Aprendendo a manipular arquivos utilizando Python.ÿÿÿ \ No newline at end of file diff --git "a/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v1.py" "b/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v1.py" new file mode 100644 index 000000000..657703d2e --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v1.py" @@ -0,0 +1,411 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + if len(conta.historico.transacoes_do_dia()) >= 2: + print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + return + + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: ('{self.cpf}')>" + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __repr__(self): + return f"<{self.__class__.__name__}: ('{self.agencia}', '{self.numero}', '{self.cliente.nome}')>" + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.utcnow().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + def transacoes_do_dia(self): + data_atual = datetime.utcnow().date() + transacoes = [] + for transacao in self._transacoes: + data_transacao = datetime.strptime(transacao["data"], "%d-%m-%Y %H:%M:%S").date() + if data_atual == data_transacao: + transacoes.append(transacao) + return transacoes + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + data_hora = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # TODO: alterar a implementação para salvar em arquivo. + # f"[{data_hora}] Função '{func.__name__}' executada com argumentos {args} e {kwargs}. Retornou {result}\n" + print(f"{data_hora}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f"\n{transacao['data']}\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta, limite=500, limite_saques=50) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git "a/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v2.py" "b/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v2.py" new file mode 100644 index 000000000..9740b0c5c --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/desafio/desafio_v2.py" @@ -0,0 +1,416 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + if len(conta.historico.transacoes_do_dia()) >= 2: + print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + return + + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: ('{self.nome}', '{self.cpf}')>" + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __repr__(self): + return f"<{self.__class__.__name__}: ('{self.agencia}', '{self.numero}', '{self.cliente.nome}')>" + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.utcnow().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + def transacoes_do_dia(self): + data_atual = datetime.utcnow().date() + transacoes = [] + for transacao in self._transacoes: + data_transacao = datetime.strptime(transacao["data"], "%d-%m-%Y %H:%M:%S").date() + if data_atual == data_transacao: + transacoes.append(transacao) + return transacoes + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + data_hora = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + with open(ROOT_PATH / "log.txt", "a") as arquivo: + arquivo.write( + f"[{data_hora}] Função '{func.__name__}' executada com argumentos {args} e {kwargs}. " + f"Retornou {resultado}\n" + ) + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f"\n{transacao['data']}\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta, limite=500, limite_saques=50) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git "a/05 - Manipula\303\247\303\243o de arquivos/desafio/log.txt" "b/05 - Manipula\303\247\303\243o de arquivos/desafio/log.txt" new file mode 100644 index 000000000..19190e38b --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/desafio/log.txt" @@ -0,0 +1,6 @@ +[2023-10-04 19:56:53] Função 'criar_cliente' executada com argumentos ([],) e {}. Retornou None +[2023-10-04 19:57:40] Função 'criar_conta' executada com argumentos (1, [], []) e {}. Retornou None +[2023-10-04 19:57:46] Função 'depositar' executada com argumentos ([],) e {}. Retornou None +[2023-10-04 20:00:03] Função 'criar_conta' executada com argumentos (1, [], []) e {}. Retornou None +[2023-10-04 20:00:14] Função 'criar_cliente' executada com argumentos ([],) e {}. Retornou None +[2023-10-04 20:05:27] Função 'criar_cliente' executada com argumentos ([],) e {}. Retornou None diff --git "a/05 - Manipula\303\247\303\243o de arquivos/lorem.txt" "b/05 - Manipula\303\247\303\243o de arquivos/lorem.txt" new file mode 100644 index 000000000..940e15212 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/lorem.txt" @@ -0,0 +1,2 @@ +What is Lorem Ipsum? +Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. \ No newline at end of file diff --git "a/05 - Manipula\303\247\303\243o de arquivos/novo-diretorio/novo.txt" "b/05 - Manipula\303\247\303\243o de arquivos/novo-diretorio/novo.txt" new file mode 100644 index 000000000..e69de29bb diff --git "a/05 - Manipula\303\247\303\243o de arquivos/teste.txt" "b/05 - Manipula\303\247\303\243o de arquivos/teste.txt" new file mode 100644 index 000000000..60c2a1321 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/teste.txt" @@ -0,0 +1,5 @@ +Escrevendo dados em um novo arquivo. +escrevendo +um +novo +texto \ No newline at end of file diff --git "a/05 - Manipula\303\247\303\243o de arquivos/usuarios.csv" "b/05 - Manipula\303\247\303\243o de arquivos/usuarios.csv" new file mode 100644 index 000000000..906f94d16 --- /dev/null +++ "b/05 - Manipula\303\247\303\243o de arquivos/usuarios.csv" @@ -0,0 +1,3 @@ +id,nome +1,Maria +2,João From c1db0920b8207dac41239b40bc3e1a30c314e94b Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 17 Jan 2024 12:39:59 -0300 Subject: [PATCH 08/16] feat: add module 6 files --- .../01_boas_praticas.py" | 26 ++ .../Pipfile" | 11 + .../Pipfile.lock" | 20 + .../desafio/desafio_v1.py" | 404 ++++++++++++++++++ .../desafio/desafio_v2.py" | 404 ++++++++++++++++++ .../poetry.lock" | 7 + .../pyproject.toml" | 15 + 7 files changed, 887 insertions(+) create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/01_boas_praticas.py" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile.lock" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v1.py" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v2.py" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/poetry.lock" create mode 100644 "06 - Gerenciamento de pacotes e boas pr\303\241ticas/pyproject.toml" diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/01_boas_praticas.py" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/01_boas_praticas.py" new file mode 100644 index 000000000..57c0ae4bc --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/01_boas_praticas.py" @@ -0,0 +1,26 @@ +import os +import sys + +print(os) +print(sys) + +a = "python" + +b = "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + +frutas = [ + "pera", + "maçã", + "laranja", + "uva", + "melão", + "morango", + "abacate", + "banana", + "carambola", + "pessego", + "tamara", + "melancia", +] + +carros = ["ferrari", "brasilia", "gol", "up"] diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile" new file mode 100644 index 000000000..0757494bb --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile" @@ -0,0 +1,11 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.11" diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile.lock" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile.lock" new file mode 100644 index 000000000..54a707836 --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/Pipfile.lock" @@ -0,0 +1,20 @@ +{ + "_meta": { + "hash": { + "sha256": "ed6d5d614626ae28e274e453164affb26694755170ccab3aa5866f093d51d3e4" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": {} +} diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v1.py" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v1.py" new file mode 100644 index 000000000..396745193 --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v1.py" @@ -0,0 +1,404 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class contasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + if len(conta.historico.transacoes_do_dia()) >= 2: + print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + return + + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + def transacoes_do_dia(self): + data_atual = datetime.utcnow().date() + transacoes = [] + for transacao in self._transacoes: + data_transacao = datetime.strptime(transacao["data"], "%d-%m-%Y %H:%M:%S").date() + if data_atual == data_transacao: + transacoes.append(transacao) + return transacoes + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + print(f"{datetime.now()}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibirExtrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f'\n{transacao["tipo"]}:\n\tR$ {transacao["valor"]:.2f}' + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criarCliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criarConta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + # NOTE: O valor padrão de limite de saques foi alterado para 50 saques + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta, limite=500, limite_saques=50) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listarContas(contas): + for conta in contasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibirExtrato(clientes) + + elif opcao == "nu": + criarCliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criarConta(numero_conta, clientes, contas) + + elif opcao == "lc": + listarContas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v2.py" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v2.py" new file mode 100644 index 000000000..8df25bec9 --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/desafio/desafio_v2.py" @@ -0,0 +1,404 @@ +import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime + + +class ContasIterador: + def __init__(self, contas): + self.contas = contas + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + conta = self.contas[self._index] + return f"""\ + Agência:\t{conta.agencia} + Número:\t\t{conta.numero} + Titular:\t{conta.cliente.nome} + Saldo:\t\tR$ {conta.saldo:.2f} + """ + except IndexError: + raise StopIteration + finally: + self._index += 1 + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + self.indice_conta = 0 + + def realizar_transacao(self, conta, transacao): + if len(conta.historico.transacoes_do_dia()) >= 2: + print("\n@@@ Você excedeu o número de transações permitidas para hoje! @@@") + return + + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + saldo = self.saldo + excedeu_saldo = valor > saldo + + if excedeu_saldo: + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + + elif valor > 0: + self._saldo -= valor + print("\n=== Saque realizado com sucesso! ===") + return True + + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + + return False + + def depositar(self, valor): + if valor > 0: + self._saldo += valor + print("\n=== Depósito realizado com sucesso! ===") + else: + print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False + + return True + + +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self._limite = limite + self._limite_saques = limite_saques + + @classmethod + def nova_conta(cls, cliente, numero, limite, limite_saques): + return cls(numero, cliente, limite, limite_saques) + + def sacar(self, valor): + numero_saques = len( + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + ) + + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques + + if excedeu_limite: + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + + elif excedeu_saques: + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + + else: + return super().sacar(valor) + + return False + + def __str__(self): + return f"""\ + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} + """ + + +class Historico: + def __init__(self): + self._transacoes = [] + + @property + def transacoes(self): + return self._transacoes + + def adicionar_transacao(self, transacao): + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%S"), + } + ) + + def gerar_relatorio(self, tipo_transacao=None): + for transacao in self._transacoes: + if tipo_transacao is None or transacao["tipo"].lower() == tipo_transacao.lower(): + yield transacao + + def transacoes_do_dia(self): + data_atual = datetime.utcnow().date() + transacoes = [] + for transacao in self._transacoes: + data_transacao = datetime.strptime(transacao["data"], "%d-%m-%Y %H:%M:%S").date() + if data_atual == data_transacao: + transacoes.append(transacao) + return transacoes + + +class Transacao(ABC): + @property + @abstractproperty + def valor(self): + pass + + @abstractclassmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.sacar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso_transacao = conta.depositar(self.valor) + + if sucesso_transacao: + conta.historico.adicionar_transacao(self) + + +def log_transacao(func): + def envelope(*args, **kwargs): + resultado = func(*args, **kwargs) + print(f"{datetime.now()}: {func.__name__.upper()}") + return resultado + + return envelope + + +def menu(): + menu = """\n + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ + + return input(textwrap.dedent(menu)) + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None + + +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta + return cliente.contas[0] + + +@log_transacao +def depositar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do depósito: ")) + transacao = Deposito(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def sacar(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + cliente.realizar_transacao(conta, transacao) + + +@log_transacao +def exibir_extrato(clientes): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado! @@@") + return + + conta = recuperar_conta_cliente(cliente) + if not conta: + return + + print("\n================ EXTRATO ================") + extrato = "" + tem_transacao = False + for transacao in conta.historico.gerar_relatorio(): + tem_transacao = True + extrato += f'\n{transacao["tipo"]}:\n\tR$ {transacao["valor"]:.2f}' + + if not tem_transacao: + extrato = "Não foram realizadas movimentações" + + print(extrato) + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + + +@log_transacao +def criar_cliente(clientes): + cpf = input("Informe o CPF (somente número): ") + cliente = filtrar_cliente(cpf, clientes) + + if cliente: + print("\n@@@ Já existe cliente com esse CPF! @@@") + return + + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + + cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) + + clientes.append(cliente) + + print("\n=== Cliente criado com sucesso! ===") + + +@log_transacao +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + return + + # NOTE: O valor padrão de limite de saques foi alterado para 50 saques + conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta, limite=500, limite_saques=50) + contas.append(conta) + cliente.contas.append(conta) + + print("\n=== Conta criada com sucesso! ===") + + +def listar_contas(contas): + for conta in ContasIterador(contas): + print("=" * 100) + print(textwrap.dedent(str(conta))) + + +def main(): + clientes = [] + contas = [] + + while True: + opcao = menu() + + if opcao == "d": + depositar(clientes) + + elif opcao == "s": + sacar(clientes) + + elif opcao == "e": + exibir_extrato(clientes) + + elif opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) + + elif opcao == "q": + break + + else: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/poetry.lock" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/poetry.lock" new file mode 100644 index 000000000..5dfbaad43 --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/poetry.lock" @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "81b2fa642d7f2d1219cf80112ace12d689d053d81be7f7addb98144d56fc0fb2" diff --git "a/06 - Gerenciamento de pacotes e boas pr\303\241ticas/pyproject.toml" "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/pyproject.toml" new file mode 100644 index 000000000..8c5ec01a5 --- /dev/null +++ "b/06 - Gerenciamento de pacotes e boas pr\303\241ticas/pyproject.toml" @@ -0,0 +1,15 @@ +[tool.poetry] +name = "01 - ambiente virtual" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" +packages = [{include = "01 _ ambiente virtual"}] + +[tool.poetry.dependencies] +python = "^3.11" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 4b5491ce15b713325f191b4b50433ffae930d8c4 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Wed, 17 Jan 2024 12:40:11 -0300 Subject: [PATCH 09/16] feat: add module 7 files --- 07 - Banco de dados/01_dbapi.py | 64 +++++++++++++ 07 - Banco de dados/02_injecao_sql.py | 16 ++++ 07 - Banco de dados/03_transacao.py | 19 ++++ .../__pycache__/dominio.cpython-311.pyc | Bin 0 -> 1313 bytes .../__pycache__/servico.cpython-311.pyc | Bin 0 -> 3465 bytes 07 - Banco de dados/desafio/desafio_v1/bd.py | 9 ++ .../desafio/desafio_v1/dominio.py | 22 +++++ .../desafio/desafio_v1/main.py | 31 ++++++ .../desafio/desafio_v1/servico.py | 55 +++++++++++ .../desafio_v2/__pycache__/bd.cpython-311.pyc | Bin 0 -> 1787 bytes .../__pycache__/dominio.cpython-311.pyc | Bin 0 -> 2807 bytes .../__pycache__/servico.cpython-311.pyc | Bin 0 -> 6208 bytes 07 - Banco de dados/desafio/desafio_v2/bd.py | 38 ++++++++ .../desafio/desafio_v2/db.sqlite | Bin 0 -> 28672 bytes .../desafio/desafio_v2/dominio.py | 52 ++++++++++ .../desafio/desafio_v2/main.py | 42 +++++++++ .../desafio/desafio_v2/servico.py | 89 ++++++++++++++++++ 07 - Banco de dados/meu_banco.sqlite | Bin 0 -> 12288 bytes 18 files changed, 437 insertions(+) create mode 100644 07 - Banco de dados/01_dbapi.py create mode 100644 07 - Banco de dados/02_injecao_sql.py create mode 100644 07 - Banco de dados/03_transacao.py create mode 100644 07 - Banco de dados/desafio/desafio_v1/__pycache__/dominio.cpython-311.pyc create mode 100644 07 - Banco de dados/desafio/desafio_v1/__pycache__/servico.cpython-311.pyc create mode 100644 07 - Banco de dados/desafio/desafio_v1/bd.py create mode 100644 07 - Banco de dados/desafio/desafio_v1/dominio.py create mode 100644 07 - Banco de dados/desafio/desafio_v1/main.py create mode 100644 07 - Banco de dados/desafio/desafio_v1/servico.py create mode 100644 07 - Banco de dados/desafio/desafio_v2/__pycache__/bd.cpython-311.pyc create mode 100644 07 - Banco de dados/desafio/desafio_v2/__pycache__/dominio.cpython-311.pyc create mode 100644 07 - Banco de dados/desafio/desafio_v2/__pycache__/servico.cpython-311.pyc create mode 100644 07 - Banco de dados/desafio/desafio_v2/bd.py create mode 100644 07 - Banco de dados/desafio/desafio_v2/db.sqlite create mode 100644 07 - Banco de dados/desafio/desafio_v2/dominio.py create mode 100644 07 - Banco de dados/desafio/desafio_v2/main.py create mode 100644 07 - Banco de dados/desafio/desafio_v2/servico.py create mode 100644 07 - Banco de dados/meu_banco.sqlite diff --git a/07 - Banco de dados/01_dbapi.py b/07 - Banco de dados/01_dbapi.py new file mode 100644 index 000000000..294343686 --- /dev/null +++ b/07 - Banco de dados/01_dbapi.py @@ -0,0 +1,64 @@ +import sqlite3 +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +conexao = sqlite3.connect(ROOT_PATH / "meu_banco.sqlite") +cursor = conexao.cursor() +cursor.row_factory = sqlite3.Row + + +def criar_tabela(conexao, cursor): + cursor.execute( + "CREATE TABLE clientes (id INTEGER PRIMARY KEY AUTOINCREMENT, nome VARCHAR(100), email VARCHAR(150))" + ) + conexao.commit() + + +def inserir_registro(conexao, cursor, nome, email): + data = (nome, email) + cursor.execute("INSERT INTO clientes (nome, email) VALUES (?,?);", data) + conexao.commit() + + +def atualizar_registro(conexao, cursor, nome, email, id): + data = (nome, email, id) + cursor.execute("UPDATE clientes SET nome=?, email=? WHERE id=?;", data) + conexao.commit() + + +def excluir_registro(conexao, cursor, id): + data = (id,) + cursor.execute("DELETE FROM clientes WHERE id=?;", data) + conexao.commit() + + +def inserir_muitos(conexao, cursor, dados): + cursor.executemany("INSERT INTO clientes (nome, email) VALUES (?,?)", dados) + conexao.commit() + + +def recuperar_cliente(cursor, id): + cursor.execute("SELECT email, id, nome FROM clientes WHERE id=?", (id,)) + return cursor.fetchone() + + +def listar_clientes(cursor): + return cursor.execute("SELECT * FROM clientes ORDER BY nome DESC;") + + +clientes = listar_clientes(cursor) +for cliente in clientes: + print(dict(cliente)) + +cliente = recuperar_cliente(cursor, 2) +print(dict(cliente)) +print(cliente["id"], cliente["nome"], cliente["email"]) +print(f'Seja bem vindo ao sistema {cliente["nome"]}') + +# dados = [ +# ("Guilherme", "guilherme@gmail.com"), +# ("Chappie", "chappie@gmail.com"), +# ("Melaine", "melaine@gmail.com"), +# ] +# inserir_muitos(conexao, cursor, dados) diff --git a/07 - Banco de dados/02_injecao_sql.py b/07 - Banco de dados/02_injecao_sql.py new file mode 100644 index 000000000..977022a5a --- /dev/null +++ b/07 - Banco de dados/02_injecao_sql.py @@ -0,0 +1,16 @@ +import sqlite3 +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +conexao = sqlite3.connect(ROOT_PATH / "meu_banco.sqlite") +cursor = conexao.cursor() +cursor.row_factory = sqlite3.Row + +id_cliente = input("Informe o id do cliente: ") +cursor.execute(f"SELECT * FROM clientes WHERE id={id_cliente}") + +clientes = cursor.fetchall() + +for cliente in clientes: + print(dict(cliente)) diff --git a/07 - Banco de dados/03_transacao.py b/07 - Banco de dados/03_transacao.py new file mode 100644 index 000000000..42204e82e --- /dev/null +++ b/07 - Banco de dados/03_transacao.py @@ -0,0 +1,19 @@ +import sqlite3 +from pathlib import Path + +ROOT_PATH = Path(__file__).parent + +conexao = sqlite3.connect(ROOT_PATH / "meu_banco.sqlite") +cursor = conexao.cursor() +cursor.row_factory = sqlite3.Row + +try: + cursor.execute("DELETE FROM clientes WHERE id = 8;") + conexao.commit() + + cursor.execute("INSERT INTO clientes (nome, email) VALUES (?,?)", ("Teste 3", "teste3@gmail.com")) + cursor.execute("INSERT INTO clientes (id, nome, email) VALUES (?,?,?)", (2, "Teste 4", "teste4@gmail.com")) + conexao.commit() +except Exception as exc: + print(f"Ops! um erro ocorreu! {exc}") + conexao.rollback() diff --git a/07 - Banco de dados/desafio/desafio_v1/__pycache__/dominio.cpython-311.pyc b/07 - Banco de dados/desafio/desafio_v1/__pycache__/dominio.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0b3f8ff61228c1cd7e56485692865f677cb8837 GIT binary patch literal 1313 zcmb_c&1xGl5Z3ah%dapoAV;XcLEoKq-Y>gs|jf!>;5?>q2g8 z$OGh1$Qz`DT>1)q1LtP;ROqQUhxC+FXLjviyN4cHS^6}RzHg)%X8om9nkLW|zCIBz zEJA+ZV%V|+y)!r5S^pPtpY3Ownl zwbqHwz?})WV3?->(a7(6VJ50a^-jGvGav0nyPe%$aW>kCb~-Sk9X&f#|3jKHkowUy z7x#rRioNlS*I=2x*wVcbV0BydCRR7Z8dFC4e~7^c4&B3~nS( zoI`722*@_;0Ary}m!j=xyR+TP7o)9ctFzTxSn9giuM-3#b%uWaW4tqIy!E#BI7VE; zh-s6_kQomW(2%X;c)ke*rlGK8sGx43miF`_#xoJ)GVUSZBh7Pwu^1mLc3te(?qcsg z-ld0K7yEUBV5DK97p&;Q{kJ5H#xE$XnIxOZb>mOC%WbA*|585!GW>}U^9`WySeDfz zkGtc&pCxvoZ#&jppZp0oxc3X;#sS{xa>^_YG-cFG!r2C@%BU)jsLK7JY6Vr3aP|RJ OE2vr-QH{`&to#NW=Pi@~ literal 0 HcmV?d00001 diff --git a/07 - Banco de dados/desafio/desafio_v1/__pycache__/servico.cpython-311.pyc b/07 - Banco de dados/desafio/desafio_v1/__pycache__/servico.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c1a6a7457287dda988476c7b6461e349040e9bc GIT binary patch literal 3465 zcmd^BL2MI86rJ7m+Kyv8i4z=>CSgN?;zBS1N|cDu(3Bt$a-&97r5w6idDdj<+8buq zQDYg20|z5my1n-QEi_5a;<5<7wR-tqd+pa1{N z{y+2f|M?>pix6n1pB_|y2odr(4w^0CGjC^rxkD(SoKEuGBF7=m>wI2V6!L+^KwexF zIl_|*gbFta4czAmc?5s2Ee5H$KvKciI81R)aJI-ygQWx~G^=ZB!BU;*ylR?;GN+kZ zR&nCq=u(ks6vkP99k&zx!CMN<9ioy&j*>;5a;iXiD&zt*a1%VDqAF5e4N{>5QGqlF z(vU9=!#D)v4u2ehaTvy(w3Bu~AB8>weT)j=chrgdZZ4>7MavpaFzW{31et0TSs|0% z3>ADrUV#7GKG(rAS0*xWUZZ4zJmfMdj&*<_#XA90)pPE^#EFvhKA&DP@@o1@QPY=H z2HZR|uBw({rm1G6vj)|!7-KmDb}el&uu;aASFI(ZFox>m6VjOUl~TwW5>+LtP@JZk zN)B{>FRzTJO)rpR%d1XUmbHRr$#SW`HOga61?(R&V?b_@dUB`|xZPQS-&+t6r-K^V zVjhmrXxRe}bnYGmfkEIb5EcdYA%x6uDb@qTkC?@Qce-+#ZZU<)Sq~DWkv4bQ^oUv$ zT?4Pe(Mo`I1K;9A7zC~pe*$xdSm>1BD0d%5k2WROc?&K5)?&%GTDFd^3oW)nn>Ns* z4YX-RtBGZJZ_xvPOPc2V6`^ z?+2SIv#3Z+El@>*Gcgq%biJE8wJ521MMGh7yU!KGe>tUKDW;}ONh}Hb%=(e+MKS;cFcd=!$xSmS zkS#1SCi7%e}Yld z9K$koAUl9GH2V8N`|#J*{pV}@&sT@$YD05&c;Mqm8m=?UJB>B&uI$3glp$qXvaz3p>hs@KXp(q5ToJ=@a|ZA*7p=og5E zdDd_3Yf#}wxB9Vucx#|(wY@KMkK0opUr2)Ww)D4Uznk@T56e3|+^(f!Ym)W+PX#k2 zshRSiGB#_Lba-}tEzm%Giic{c3WM*bYy7c^4N&QJPEl9FnF~VdkK`w%ly35>A z31+?Sgy~S%v9Og6M5d>wB_C%J<}_-!Yna6>rnFHB^Z;xLj2Ye&9C4Xx1G`C*=ELA`BpBdKoj;k}yj{ZP;$n;&)^I0tBGkH((E*srF6R`lc%#^+fVT z;?R2H(7n}aV!W0ZuY_K9CNQn_ZbW+J2bcfVcl z+-G<0t0xCvBq!FB6Kg3uIZ;hst|c#5E>teOMET@;a&qmWot&&DGqq%5eDf?#DXUP$ zgu4b@jz;0cJ_keS+=_v3^>?;Q2Lx=@B6G)uq)mSsrka>7V0SUB6V>p^TKJ^heB7|$ z`hypjOe&5SuL9&jS#~0_oHuAuM?Nac*B~i+8is+hE+CEo4m13`T)4Q^)x!y0IOpr-~a#s literal 0 HcmV?d00001 diff --git a/07 - Banco de dados/desafio/desafio_v1/bd.py b/07 - Banco de dados/desafio/desafio_v1/bd.py new file mode 100644 index 000000000..a762736b1 --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v1/bd.py @@ -0,0 +1,9 @@ +from sqlite3 import Connection, Cursor + + +def criar_bd(cursor: Cursor) -> None: + pass + + +def criar_conexao() -> Connection: + pass diff --git a/07 - Banco de dados/desafio/desafio_v1/dominio.py b/07 - Banco de dados/desafio/desafio_v1/dominio.py new file mode 100644 index 000000000..b4328c865 --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v1/dominio.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass + + +@dataclass +class Cliente: + email: str + telefone: str + status: str + + +@dataclass +class PessoaFisica(Cliente): + nome: str + cpf: str + renda_mensal: float + + +@dataclass +class PessoaJuridica(Cliente): + nome_fantasia: str + cnpj: str + faturamento_anual: float diff --git a/07 - Banco de dados/desafio/desafio_v1/main.py b/07 - Banco de dados/desafio/desafio_v1/main.py new file mode 100644 index 000000000..621628a39 --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v1/main.py @@ -0,0 +1,31 @@ +import textwrap + +from servico import ClienteServico + + +def menu(): + menu = """\n + ================ MENU ================ + [1]\tNovo cliente + [2]\tListar clientes + [0]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def main(): + servico = ClienteServico(cursor=None) + + while True: + match menu(): + case "1": + servico.criar_cliente() + case "2": + servico.listar_clientes() + case "0": + break + case _: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + +main() diff --git a/07 - Banco de dados/desafio/desafio_v1/servico.py b/07 - Banco de dados/desafio/desafio_v1/servico.py new file mode 100644 index 000000000..03634544e --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v1/servico.py @@ -0,0 +1,55 @@ +from sqlite3 import Cursor + +from dominio import Cliente, PessoaFisica, PessoaJuridica + + +class ClienteServico: + def __init__(self, cursor: Cursor) -> None: + self.cursor = cursor + + def filtrar_cliente(self, documento: str) -> Cliente | None: + pass + + def _criar_cliente_pessoa_fisica(self, documento: str) -> PessoaFisica: + nome = input("Informe o nome completo: ") + renda_mensal = float(input("Informe sua renda mensal: ")) + email = input("Informe seu email: ") + telefone = input("Informe seu telefone: ") + + return PessoaFisica( + nome=nome, cpf=documento, renda_mensal=renda_mensal, email=email, telefone=telefone, status="ativo" + ) + + def _criar_cliente_pessoa_juridica(self, documento: str) -> PessoaJuridica: + nome = input("Informe o nome fantasia: ") + faturamento_anual = float(input("Informe sua renda mensal: ")) + email = input("Informe seu email: ") + telefone = input("Informe seu telefone: ") + + return PessoaJuridica( + nome_fantasia=nome, + cnpj=documento, + faturamento_anual=faturamento_anual, + email=email, + telefone=telefone, + status="ativo", + ) + + def criar_cliente(self) -> None: + documento = input("Informe o documento (CPF/CNPJ): ") + cliente = self.filtrar_cliente(documento) + + if cliente: + print("\n@@@ Já existe cliente com esse documento (CPF/CNPJ)! @@@") + return + + if len(documento) == 11: + cliente = self._criar_cliente_pessoa_fisica(documento=documento) + else: + cliente = self._criar_cliente_pessoa_juridica(documento=documento) + + print(cliente) + print("\n=== Cliente criado com sucesso! ===") + + def listar_clientes(self) -> None: + print("Não existem clientes cadastrados!") diff --git a/07 - Banco de dados/desafio/desafio_v2/__pycache__/bd.cpython-311.pyc b/07 - Banco de dados/desafio/desafio_v2/__pycache__/bd.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbb3d62bfb798aa850b57b562fade88202a79dbf GIT binary patch literal 1787 zcmcIkL66%+6dpT{vzujCE2v;aFLR*PT4I~3TdAt3iqh067Fj1`9kk8Gns_FSBgan1 zm0rVr8ik5+`m!%c&>cIGYrgb_GH_+wZ;cyf^dSH}l?i z%gbd1^y4q*S)w8Is~pS-#u**gLF^-ns1l&Alxm=i)UK*Xy%3;Lp<5VfT@A(>(`b<@ zeU0j$YuzF(vO-^_CD3)MY$IdgH@T#3D5=&WVqmCgxfX>X^MoITsa{KX9Pw25P9$d{ z;i0EwaZFhOg#3;#0^3KQpaYP9C#rl4UO&t|WvsBjYLgatm+=YJ+Gf=;u~WU#FtJs~ zcGJP;jUO!8%rObV#{{STV@-#+SXRJeH(vh-p19A)3j_@ZOgQscXJ+W zMEn3d=8luCZg(1uyn$c=>qj9w%_bH^B=Kn$9`^~2TsFctAtT4yGVx8bUhOm-T|Rs836f&y>LAI2e|a%if{tBh{z>j^1p#ItDV0{sOM#NpLvO3F04HC$llZ}aanM>k8Zr^Z73xP7FQWdPXah<>u!`VdLpH__#2^YcylMZ0BUo%V zn~vM6Iv-4N=d!n_%Tq|ejNK;@e*^x#17Ca&*dBVidgkk$FIT==d9r$L&w9G_`WNqi zbM@fi68Sddi9S){c|y=K%>Zp8d^xGjI0Iy!pLZ z|I*u=Bv5WoA7d$nkiSsbDQZX9s)2BiP(lTZ6os51pe$NqQOZdIVS-355Gt(^DsPB{ zJbya_RM#`SB{xtx^Om@s7edc%z^LLHwtfeIs0u4WncQrT z0oL}~c6+1IIY+)e{S`!cMO=}}VyzqRt~m%)q*C-WYCAjMYeu4PUJ=VeS^N=pTb2No zpXKFV@FRTxURi)WtBU~%1bti3mc=vLwve2XgSf6!Gw*$8LL0pyZ8W37T7m<#1Pq6=_hmi`g4ak!7zhnbr-)LAt`7o6K|E zEH#~M-l67oXBHytv8>0z#+WVL@oqTwEDj$#rOj#|8g|~%DATAxaT;@t0*rN9{nm*r zb&95KI!8-)f|#zm9@q8d{?17r?R50Of?N}zLi~ZDb;ZwUKP^1UWFKa-jm+_8=6H*U ziJ1_-yS#q>?z?_^?B2o;3$+6^w~?M{re_|dPd-eaY@|;&)2G+Z`>DaIQXiRyS{rG% zfQ{7j`gs`aPp_x8@K6?~R1BwW6d8mwsq00DE?Fq2bp6{U!|IGku+*A3ftfM}*; zySmQD;0^Z)ABTp&hJZ{90TP?ImjNn!evf&)j7X!u(D8DSY7Z_{gwI3{OWv5 zR+N$IaEn0mLTEceACdGzUs$#l0bZG23Zz_^UQC4qi&2He3o=#Lup z=DLpYvFVz5Bam%iOCaS-g&@V5O%1)sY}c@YgmVpPqtj~~uebc(3`syon15EdK#`P5 zS)c+&uU$YBBbpS^~AW#aHV|YPNcuN-Lovss$8sGT)pTIjn-wX4SDpjGSCv^O7hopEdtFSpN7pYY)V8O zT>CP#+3jsRN0`>1HrkfUgUOTfzp88#Y`F zAsznTTt5zhHn zBR$=}JXXrWw6;_t_O!&L0N;yeVH5kFAV8*xJ!@la%kgkdp8&VUvvBv+CiXr-=*~j3$(PRh M`~|bR9>T2t2hdV!_5c6? literal 0 HcmV?d00001 diff --git a/07 - Banco de dados/desafio/desafio_v2/__pycache__/servico.cpython-311.pyc b/07 - Banco de dados/desafio/desafio_v2/__pycache__/servico.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b03776076cdd9a7da11dbe6e96b1cf4dfc9c29c GIT binary patch literal 6208 zcmd^DPi))P87D@B_$-gu1SOk? zIy=tXnVe9piHxs*ZZesSSQ6DtrZV82<+xJ;>q96#K)xjE@u8Z3qq_#b=TJbHUA&Yl z7h%gK&l(^_!)s87#C<{~QDZAq$w!d;rdh(1xAE%9!&PFfS^Chuq7EylXqC*9US%YS z($H&gQhRZF_|SMe!Xqg8rdi7STWSLPNT+}3hPukhmHDOlxhvetenYNN49dHZTs8m5vnL6 zkS~ed?r`n%-@U)_{)5Zg-6LA}NS%#tT-aepw%HMlTh*@TwNGy8tZ1;JhQDX-`s>YtGCsuka+ zR(E}>YeU@%$osqs=*``wJZ-7ATEC~(-&RYj4w}LEs-Ub@s_L)OJpCl_C;*BCe=C6U z1k;to`BGkj6V6Fo364CMlZxvFIO(Uk%AixFl(U>Hl=v(Mr%cHfPIEuSnsq{%6N*_8 z6~^6#s!$LBA%xSMNoQ5@j+6|Te$<6Yf#R7g7MQUFnlu(+G7gg&P}HniRshavv9w-R zO*&tYvZ{>cmJGDiNgjd%`2Zp#h;Tp@G(`>vOU;HdIazFJS;li1DsQ&w+^ZV*BJsC? zfC5JPwS8~tkrPJbM9sfDF!YD3+TjoM;TdCiMjx0p24-vQZgig(e^Za9jA*Jx@5bKH z5-0T7q!F8}G2ch_*L(LpIJNoS)3SCgqur7klrI|kkpS`nkp|)G3jN>dUp`w>vx=An z{Tp$sQy$bo&SI43vZXRoLKy*}Jc!7CAfR78NWU_U2-BYflrno=tQpMOQWg0iVx;6l zNXTJCknVIU*eI%cxq=&nK6s$sJ6Jz3QSTe5?;Ebi1{*y=q-a21AkrX$wxXFq8;CuKhn55*o(c(Y zCWtkNt?;mEES(Y*^JJA=YZ^~TH&GDM$SmWKr`nCETk={H#Q>*#>B{u^^ve9@E8O|? zl}nsGN^TUxMv8M_BgNUU!F@Elbaj4(8~q>!zvLO%QRM5$`w(gFBi?`;=%sve=z*k||jqV+0|2DI~&R(eZ^lw~$uwV?O9^ZUY(%9)A zX{rnM07wH9hKTlWd|^b6Xv`7o~jO77`Z|}WncYW^qsy?~bZ8O?! z6Y|Sf2o?2ihjtk}WxG!wL$A&Kby}miNt*d&vMXKT90&zh+;Nh*(YfVw6Lab1#pG$Od>T3k&CJYji{Jf`6Fw6a6?_28 z8$-ST2Yw^?@O$tcFH_CKGqy^7kCNJUn%F1>wJ)d`eej1 z6az3#lU|p_k}9JMmV5+}F+^G@gB_D$REE8iBuf1PkzWPy^;$$uStUG}mkJv8TtMy&d*=2)W*4X7b+gCgHeTUB-AKZygZO5mcCbjsK9$zxz zOSJ_oHMN~cZC=w7DLpZ5B&KVNF8{n9y;1Mo|KQs1KY2X$qmb9XTlW_EQ1Cfxbv3;)Zy>?b1O4gnvj}pM)!o3w~b)_o@p{t8G`Ec0(S+Xyt$M?FzuIIrONY7v97B8Jow-AfF1etM>=cc*!{CMm5oT-5OfG@~iX-ybe zU;9~kYi@bvH(@0)aHhZelVl@j(HV%s<+40-E70QyMoM_1>cLMT&IxzBB=%J~TPVmA zAlB^7WY=XufhP^PwO}sO506$Vc&f-$n2bF!1;d1bG!vk~zEc+f!&{dfrQ=TBR=)_$ z$`%lhx1tTL>CA0|xvep`>#>2a&+NpGZO4x3v7`}8))<#xqYV{wrf4uljVacd=)DiW zzOd6jy4^pj_a8U+aJvm{298J(Rq*jbI8eKs(H$`?qB))t`t-jSWY z(e1v`zpGmdf4lqbU9E3a@0&OJ=5_X*!JgCb*PvQl__WHugc*3eRF&deFm15ChDxVfaRd^e3kQwBqB0Q!_vLCH0QbDY3 z)-s<(S@fiZvEnD@J!`Aqm*=4j-8GaifWYY@$vQEpdB11GDedK7Bd|h}Xy7l8ezfXd KyrKyn+ None: + cursor.executescript( + """ + CREATE TABLE IF NOT EXISTS cliente ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL, + telefone TEXT NOT NULL, + status TEXT NOT NULL, + criado_em DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS pessoa_fisica ( + cliente_id INTEGER PRIMARY KEY, + nome TEXT NOT NULL, + cpf TEXT NOT NULL UNIQUE, + renda_mensal REAL NOT NULL, + FOREIGN KEY (cliente_id) REFERENCES cliente(id) + ); + + CREATE TABLE IF NOT EXISTS pessoa_juridica ( + cliente_id INTEGER PRIMARY KEY, + nome_fantasia TEXT, + cnpj TEXT NOT NULL UNIQUE, + faturamento_anual REAL NOT NULL, + FOREIGN KEY (cliente_id) REFERENCES cliente(id) + ); + """ + ) + + +def criar_conexao() -> Connection: + ROOT_PATH = Path(__file__).parent + return sqlite3.connect(ROOT_PATH / "db.sqlite") diff --git a/07 - Banco de dados/desafio/desafio_v2/db.sqlite b/07 - Banco de dados/desafio/desafio_v2/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..143a4de9cd9725092356cd603a639e80549c03de GIT binary patch literal 28672 zcmeI)J5Sp{00;21Lz0%1q^eY9VCYFy3K5TDC=yUrA`J#bO#%c*D(YYz&IODdJJ=5D zTFPhW*rh|iLe&+fj!YHb0jX7`&b_=Kj(`po3;a)Z?0Y%BpYH~rEpM#nO`p@M|eDTCX@EJq4-A)i7krtdafvtk;oFB-{~)QqN5 zE%Pd`(!NYOI!JqhVVT_axo%cyuBfRiYKg9ua)nH3lfG6r#{*?;r^fXvvwh~7jB4r| zEtqfE_1#u7Ev{;`SkC8ZxtLootHEMb<~Lkc6MyUI%x*A?iqp-X*9jJ0UM;D)m16T` z=;+1CrbV4)wWJoa>IUs3kD8VA`T3?_Mk2}eF^QOVg}?LmEO85@vxe`q;(8y7uJk2D z4@N@KWGW>chI_%Qrgy>JjtpFe-OlL6v1#i&<<*_TSY+^#TPNP9PgJUp?TT~e1b<-A6-<&qc;n%+#@&@zR!i;*xSl;y;GlK3uu5Fh{n z2tWV=5P$##AOHafKmY;|xEX;JIYJ6gk~4-Qrp|n4k$Z+?vAXMQi)k=sFlW+mYLi>8 z(h1YdH}{?Csp**sWokm1qsrXE?EHc<_gspQlsM3K!(7~MPB}Cyc55k@)tXySl;-`v zl=w}=f&c*sKmY;|fB*y_009U<00Izz!0i!;Nugn>`^H22{{NIDPH#^G(Gmzi00Izz z00bZa0SG_<0uX?}-4S>yg^4mEuQW_+hr2aSv&`LR){b-jPJp7sz77fyDe;Sl1pxvO zfB*y_009U<00Izz00bZaf!iVw4~^XKnE_}&|34v#liSijv;zVVfB*y_009U<00Izz z00ba#7X-%TFnN)d)z)_eXob`6w*-#99DVM6_%CJwHh;9A|DTb>*)ni literal 0 HcmV?d00001 diff --git a/07 - Banco de dados/desafio/desafio_v2/dominio.py b/07 - Banco de dados/desafio/desafio_v2/dominio.py new file mode 100644 index 000000000..64ee907de --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v2/dominio.py @@ -0,0 +1,52 @@ +from dataclasses import dataclass +from typing import Self + + +@dataclass +class Cliente: + email: str + telefone: str + status: str + + def __str__(self) -> str: + texto = "" + for campo, valor in self.__dict__.items(): + campo = campo.replace("_", " ").capitalize() + texto += f"{campo}: {valor}\n" + return texto + + +@dataclass +class PessoaFisica(Cliente): + nome: str + cpf: str + renda_mensal: float + + @classmethod + def converter_objeto_bd(cls, objeto_db: dict) -> Self: + return cls( + email=objeto_db["email"], + telefone=objeto_db["telefone"], + status=objeto_db["status"], + nome=objeto_db["nome"], + cpf=objeto_db["cpf"], + renda_mensal=objeto_db["renda_mensal"], + ) + + +@dataclass +class PessoaJuridica(Cliente): + nome_fantasia: str + cnpj: str + faturamento_anual: float + + @classmethod + def converter_objeto_bd(cls, objeto_db: dict) -> Self: + return cls( + email=objeto_db["email"], + telefone=objeto_db["telefone"], + status=objeto_db["status"], + nome_fantasia=objeto_db["nome_fantasia"], + cnpj=objeto_db["cnpj"], + faturamento_anual=objeto_db["faturamento_anual"], + ) diff --git a/07 - Banco de dados/desafio/desafio_v2/main.py b/07 - Banco de dados/desafio/desafio_v2/main.py new file mode 100644 index 000000000..8cdbe1a9e --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v2/main.py @@ -0,0 +1,42 @@ +import sqlite3 +import textwrap + +from bd import criar_bd, criar_conexao +from servico import ClienteServico + + +def menu(): + menu = """\n + ================ MENU ================ + [1]\tNovo cliente + [2]\tListar clientes + [0]\tSair + => """ + return input(textwrap.dedent(menu)) + + +def main(): + conexao = criar_conexao() + cursor = conexao.cursor() + cursor.row_factory = sqlite3.Row + + criar_bd(cursor=cursor) + + servico = ClienteServico(cursor=cursor) + + while True: + match menu(): + case "1": + servico.criar_cliente() + conexao.commit() + case "2": + servico.listar_clientes() + case "0": + break + case _: + print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") + + conexao.close() + + +main() diff --git a/07 - Banco de dados/desafio/desafio_v2/servico.py b/07 - Banco de dados/desafio/desafio_v2/servico.py new file mode 100644 index 000000000..0e760c8c9 --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v2/servico.py @@ -0,0 +1,89 @@ +from sqlite3 import Cursor + +from dominio import Cliente, PessoaFisica, PessoaJuridica + + +class ClienteServico: + def __init__(self, cursor: Cursor) -> None: + self.cursor = cursor + + def filtrar_cliente(self, documento: str) -> int: + if len(documento) == 11: + self.cursor.execute("SELECT COUNT(*) AS total FROM pessoa_fisica WHERE cpf=?;", (documento,)) + else: + self.cursor.execute("SELECT COUNT(*) AS total FROM pessoa_juridica WHERE cnpj=?;", (documento,)) + return self.cursor.fetchone()["total"] + + def _criar_cliente_pessoa_fisica(self, documento: str) -> PessoaFisica: + nome = input("Informe o nome completo: ") + renda_mensal = float(input("Informe sua renda mensal: ")) + email = input("Informe seu email: ") + telefone = input("Informe seu telefone: ") + + return PessoaFisica( + nome=nome, cpf=documento, renda_mensal=renda_mensal, email=email, telefone=telefone, status="ativo" + ) + + def _criar_cliente_pessoa_juridica(self, documento: str) -> PessoaJuridica: + nome = input("Informe o nome fantasia: ") + faturamento_anual = float(input("Informe seu faturamento anual: ")) + email = input("Informe seu email: ") + telefone = input("Informe seu telefone: ") + + return PessoaJuridica( + nome_fantasia=nome, + cnpj=documento, + faturamento_anual=faturamento_anual, + email=email, + telefone=telefone, + status="ativo", + ) + + def _criar_cliente(self, cliente: Cliente) -> int: + self.cursor.execute( + "INSERT INTO cliente (email, telefone, status) VALUES (?,?,?);", + (cliente.email, cliente.telefone, cliente.status), + ) + return self.cursor.lastrowid + + def criar_cliente(self) -> None: + documento = input("Informe o documento (CPF/CNPJ): ") + existe_cliente = self.filtrar_cliente(documento) + + if existe_cliente: + print("\n@@@ Já existe cliente com esse documento (CPF/CNPJ)! @@@") + return + + if len(documento) == 11: + cliente = self._criar_cliente_pessoa_fisica(documento=documento) + cliente_id = self._criar_cliente(cliente=cliente) + self.cursor.execute( + "INSERT INTO pessoa_fisica (cliente_id, nome, cpf, renda_mensal) VALUES (?,?,?,?)", + (cliente_id, cliente.nome, cliente.cpf, cliente.renda_mensal), + ) + else: + cliente = self._criar_cliente_pessoa_juridica(documento=documento) + cliente_id = self._criar_cliente(cliente=cliente) + self.cursor.execute( + "INSERT INTO pessoa_juridica (cliente_id, nome_fantasia, cnpj, faturamento_anual) VALUES (?,?,?,?)", + (cliente_id, cliente.nome_fantasia, cliente.cnpj, cliente.faturamento_anual), + ) + + print("\n=== Cliente criado com sucesso! ===") + + def listar_clientes(self) -> None: + self.cursor.execute("SELECT * FROM pessoa_fisica pf INNER JOIN cliente c ON c.id = pf.cliente_id;") + clientes = self.cursor.fetchall() + self.cursor.execute("SELECT * FROM pessoa_juridica pj INNER JOIN cliente c ON c.id = pj.cliente_id;") + clientes += self.cursor.fetchall() + + if not clientes: + print("\n@@@ Não existem clientes cadastrados! @@@") + + for cliente in clientes: + print(self._apresentar_dados(dados_cliente=dict(cliente))) + + def _apresentar_dados(self, dados_cliente: dict[str, str | int]) -> str: + if "cpf" in dados_cliente: + return PessoaFisica.converter_objeto_bd(objeto_db=dados_cliente) + return PessoaJuridica.converter_objeto_bd(objeto_db=dados_cliente) diff --git a/07 - Banco de dados/meu_banco.sqlite b/07 - Banco de dados/meu_banco.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..63d9f2ba9108c8e2889db5ee54d672a86c20d62d GIT binary patch literal 12288 zcmeI&$xgyR7zgkfsLMeaZyba;@gfAm65^fOG)M%*QcXCS5l3j!g+1Zmfv50EJoqHO zhfm;CY=~UEqWMod{bu=Q()@awq@A0FGnM=)7<*#ME98h!N~)X_LK3RRRgDT&4K3=Z z81{cF2~zOi4pml+1WSkLmfMoucVnd1)R*pvOJvCcS)X{1=LIl)}` zzTk!!&xJb(`riZV#PIx2*#}WK3Irek0SG_<0uX=z1Rwwb2tWV=|3=_|#?v(VnPC4D DFQkGH literal 0 HcmV?d00001 From 7736b241435ab1acf12201924197355fbe6abf1d Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Sat, 27 Apr 2024 15:29:28 -0300 Subject: [PATCH 10/16] feat: add module 11 files --- .../desafio/.gitignore | 170 +++++ .../desafio/migrations/README | 1 + .../desafio/migrations/alembic.ini | 50 ++ .../desafio/migrations/env.py | 113 +++ .../desafio/migrations/script.py.mako | 24 + .../0e07d3013f88_initial_migration.py | 49 ++ .../desafio/poetry.lock | 642 ++++++++++++++++++ .../desafio/pyproject.toml | 21 + .../desafio/src/app.py | 73 ++ .../desafio/src/config.py | 19 + .../desafio/src/controllers/account.py | 41 ++ .../desafio/src/controllers/user.py | 63 ++ .../desafio/src/models/__init__.py | 5 + .../desafio/src/models/account.py | 19 + .../desafio/src/models/base.py | 9 + .../desafio/src/models/user.py | 19 + .../desafio/src/services/account.py | 18 + .../desafio/src/services/user.py | 19 + .../desafio/src/views/account.py | 20 + .../desafio/src/views/user.py | 21 + .../dio_bank | 1 + 21 files changed, 1397 insertions(+) create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/.gitignore create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/migrations/README create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/migrations/alembic.ini create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/migrations/env.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/migrations/script.py.mako create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/migrations/versions/0e07d3013f88_initial_migration.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/poetry.lock create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/pyproject.toml create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/app.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/config.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/account.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/user.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/models/__init__.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/models/account.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/models/base.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/models/user.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/services/account.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/services/user.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/views/account.py create mode 100644 11 - Desenvolvimento de APIs com Flask/desafio/src/views/user.py create mode 160000 11 - Desenvolvimento de APIs com Flask/dio_bank diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/.gitignore b/11 - Desenvolvimento de APIs com Flask/desafio/.gitignore new file mode 100644 index 000000000..7ba1d5f23 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/.gitignore @@ -0,0 +1,170 @@ +# Created by https://www.toptal.com/developers/gitignore/api/flask +# Edit at https://www.toptal.com/developers/gitignore?templates=flask + +### Flask ### +instance/* +!instance/.gitignore +.webassets-cache +.env + +### Flask.Python Stack ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# End of https://www.toptal.com/developers/gitignore/api/flask \ No newline at end of file diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/migrations/README b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/README new file mode 100644 index 000000000..0e0484415 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/migrations/alembic.ini b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/alembic.ini new file mode 100644 index 000000000..ec9d45c26 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/migrations/env.py b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/env.py new file mode 100644 index 000000000..4c9709271 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/migrations/script.py.mako b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/migrations/versions/0e07d3013f88_initial_migration.py b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/versions/0e07d3013f88_initial_migration.py new file mode 100644 index 000000000..f9ed30763 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/migrations/versions/0e07d3013f88_initial_migration.py @@ -0,0 +1,49 @@ +"""Initial migration. + +Revision ID: 0e07d3013f88 +Revises: +Create Date: 2024-04-02 11:59:57.449258 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0e07d3013f88' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('password', sa.String(), nullable=False), + sa.Column('active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('name') + ) + op.create_table('account', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('agency', sa.String(length=4), nullable=False), + sa.Column('account_number', sa.String(length=10), nullable=False), + sa.Column('active', sa.Boolean(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('account_number'), + sa.UniqueConstraint('user_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('account') + op.drop_table('user') + # ### end Alembic commands ### diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/poetry.lock b/11 - Desenvolvimento de APIs com Flask/desafio/poetry.lock new file mode 100644 index 000000000..b326e6bf2 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/poetry.lock @@ -0,0 +1,642 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "alembic" +version = "1.13.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "apispec" +version = "6.6.0" +description = "A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)." +optional = false +python-versions = ">=3.8" +files = [ + {file = "apispec-6.6.0-py3-none-any.whl", hash = "sha256:b5b22f5dba6cc69bd90e1075de20ae3d27b310a6250c66b0271aa50615eee72d"}, + {file = "apispec-6.6.0.tar.gz", hash = "sha256:c0846f8eaa5119c46b2ecfe9bc24ed19dba8845f8655d00b51ddd296a10ea4cb"}, +] + +[package.dependencies] +packaging = ">=21.3" +PyYAML = {version = ">=3.10", optional = true, markers = "extra == \"yaml\""} + +[package.extras] +dev = ["apispec[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["apispec[marshmallow]", "pyyaml (==6.0.1)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-rtd-theme (==2.0.0)"] +marshmallow = ["marshmallow (>=3.18.0)"] +tests = ["apispec[marshmallow,yaml]", "openapi-spec-validator (==0.7.1)", "pytest"] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "apispec-webframeworks" +version = "1.1.0" +description = "Web framework plugins for apispec." +optional = false +python-versions = ">=3.8" +files = [ + {file = "apispec_webframeworks-1.1.0-py3-none-any.whl", hash = "sha256:93e7921426dbafe311d0e9dd912b6dd2e09222e153446a1a73f4c06164107582"}, + {file = "apispec_webframeworks-1.1.0.tar.gz", hash = "sha256:b095df4afb3d773bdf60f0a599b808fd2ca316a8e452f8ce23b10b4cb0e717bf"}, +] + +[package.dependencies] +apispec = {version = ">=6.0.0", extras = ["yaml"]} + +[package.extras] +dev = ["apispec-webframeworks[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +tests = ["Flask (>=2.3.3)", "aiohttp (>=3.9.3)", "bottle (>=0.12.25)", "pytest", "tornado (>=6)"] + +[[package]] +name = "bcrypt" +version = "4.1.2" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, + {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, + {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, + {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, + {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, + {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, + {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "blinker" +version = "1.7.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, + {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "flask" +version = "3.0.2" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, + {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-bcrypt" +version = "1.0.1" +description = "Brcrypt hashing for Flask." +optional = false +python-versions = "*" +files = [ + {file = "Flask-Bcrypt-1.0.1.tar.gz", hash = "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369"}, + {file = "Flask_Bcrypt-1.0.1-py3-none-any.whl", hash = "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a"}, +] + +[package.dependencies] +bcrypt = ">=3.1.1" +Flask = "*" + +[[package]] +name = "flask-marshmallow" +version = "1.2.1" +description = "Flask + marshmallow for beautiful APIs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_marshmallow-1.2.1-py3-none-any.whl", hash = "sha256:10b5048ecfaa26f7c8d0aed7d81083164450e6be8e81c04b3d4a586b3f7b6678"}, + {file = "flask_marshmallow-1.2.1.tar.gz", hash = "sha256:00ee96399ed664963afff3b5d6ee518640b0f91dbc2aace2b5abcf32f40ef23a"}, +] + +[package.dependencies] +Flask = ">=2.2" +marshmallow = ">=3.0.0" + +[package.extras] +dev = ["flask-marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["Sphinx (==7.2.6)", "marshmallow-sqlalchemy (>=0.19.0)", "sphinx-issues (==4.0.0)"] +sqlalchemy = ["flask-sqlalchemy (>=3.0.0)", "marshmallow-sqlalchemy (>=0.29.0)"] +tests = ["flask-marshmallow[sqlalchemy]", "pytest"] + +[[package]] +name = "flask-migrate" +version = "4.0.7" +description = "SQLAlchemy database migrations for Flask applications using Alembic." +optional = false +python-versions = ">=3.6" +files = [ + {file = "Flask-Migrate-4.0.7.tar.gz", hash = "sha256:dff7dd25113c210b069af280ea713b883f3840c1e3455274745d7355778c8622"}, + {file = "Flask_Migrate-4.0.7-py3-none-any.whl", hash = "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617"}, +] + +[package.dependencies] +alembic = ">=1.9.0" +Flask = ">=0.9" +Flask-SQLAlchemy = ">=1.0" + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"}, + {file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=2.0.16" + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "mako" +version = "1.3.2" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, + {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "marshmallow" +version = "3.21.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "marshmallow-sqlalchemy" +version = "1.0.0" +description = "SQLAlchemy integration with the marshmallow (de)serialization library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow_sqlalchemy-1.0.0-py3-none-any.whl", hash = "sha256:f415d57809e3555b6323356589aba91e36e4470f35953d3a10c755ac5c3307df"}, + {file = "marshmallow_sqlalchemy-1.0.0.tar.gz", hash = "sha256:20a0f2fcdd5bddc86444fa01461f17f9b6a12a8ddd4ca8c9b34fe2f2e35d00a2"}, +] + +[package.dependencies] +marshmallow = ">=3.10.0" +SQLAlchemy = ">=1.4.40,<3.0" + +[package.extras] +dev = ["marshmallow-sqlalchemy[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)"] +tests = ["pytest (<8)", "pytest-lazy-fixture (>=0.6.2)"] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.29" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, + {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, + {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "werkzeug" +version = "3.0.2" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"}, + {file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "f68338b45ed9d3d9f1be0f783f5af53d261e46bd75bf34065eb503dbc7389f84" diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/pyproject.toml b/11 - Desenvolvimento de APIs com Flask/desafio/pyproject.toml new file mode 100644 index 000000000..d1b0e0731 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "dio-challenge" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +flask = "*" +flask-sqlalchemy = "*" +flask-migrate = "*" +flask-marshmallow = "*" +apispec-webframeworks = "*" +flask-bcrypt = "*" +marshmallow-sqlalchemy = "*" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/app.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/app.py new file mode 100644 index 000000000..e8af36f79 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/app.py @@ -0,0 +1,73 @@ +import os +from http import HTTPStatus + +from apispec import APISpec +from apispec.ext.marshmallow import MarshmallowPlugin +from apispec_webframeworks.flask import FlaskPlugin +from flask import Flask, json +from flask_bcrypt import Bcrypt +from flask_marshmallow import Marshmallow +from flask_migrate import Migrate +from sqlalchemy.exc import IntegrityError +from werkzeug.exceptions import HTTPException + +from src.models import db + +migrate = Migrate() +bcrypt = Bcrypt() +ma = Marshmallow() +spec = APISpec( + title="DIO Challenge", + version="1.0.0", + openapi_version="3.0.3", + info=dict(description="DIO Challenge"), + plugins=[FlaskPlugin(), MarshmallowPlugin()], +) + + +def create_app(environment=os.environ["ENVIRONMENT"]): + app = Flask(__name__, instance_relative_config=True) + app.config.from_object(f"src.config.{environment.title()}Config") + + try: + os.makedirs(app.instance_path) + except OSError: + pass + + # initialize extensions + db.init_app(app) + migrate.init_app(app, db) + bcrypt.init_app(app) + ma.init_app(app) + + # register blueprints + from src.controllers import account, user + + app.register_blueprint(user.app) + app.register_blueprint(account.app) + + @app.route("/docs") + def docs(): + return spec.path(view=user.create_user).path(view=user.list_users).path(view=account.create_account).to_dict() + + @app.errorhandler(IntegrityError) + def handle_integrity_exception(e): + _exc = HTTPException(str(e.orig)) + _exc.code = HTTPStatus.CONFLICT + return handle_exception(_exc) + + @app.errorhandler(HTTPException) + def handle_exception(e): + """Return JSON instead of HTML for HTTP errors.""" + response = e.get_response() + response.data = json.dumps( + { + "code": e.code, + "name": e.name, + "description": e.description, + } + ) + response.content_type = "application/json" + return response + + return app diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/config.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/config.py new file mode 100644 index 000000000..5dfe18f66 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/config.py @@ -0,0 +1,19 @@ +import os + + +class Config: + TESTING = False + SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL") + + +class ProductionConfig(Config): + pass + + +class DevelopmentConfig(Config): + SQLALCHEMY_DATABASE_URI = "sqlite:///bank.sqlite" + + +class TestingConfig(Config): + TESTING = True + SQLALCHEMY_DATABASE_URI = "sqlite://" diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/account.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/account.py new file mode 100644 index 000000000..5c9b308b0 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/account.py @@ -0,0 +1,41 @@ +from http import HTTPStatus + +from flask import Blueprint, request +from marshmallow import ValidationError + +from src.services.account import AccountService +from src.views.account import AccountSchema + +app = Blueprint("account", __name__, url_prefix="/accounts") + + +@app.route("/", methods=["POST"]) +def create_account(): + """Account create view. + --- + post: + tags: + - account + summary: Add a new account + requestBody: + description: Create a new account in the bank + content: + application/json: + schema: CreateAccountSchema + required: true + responses: + 201: + description: Successful operation + content: + application/json: + schema: AccountSchema + """ + service = AccountService() + account_schema = AccountSchema() + + try: + account = service.create(account_data=request.json) + except ValidationError as exc: + return exc.messages, HTTPStatus.UNPROCESSABLE_ENTITY + + return account_schema.dump(account), HTTPStatus.CREATED diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/user.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/user.py new file mode 100644 index 000000000..a38306f4a --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/controllers/user.py @@ -0,0 +1,63 @@ +from http import HTTPStatus + +from flask import Blueprint, request +from marshmallow import ValidationError + +from src.services.user import UserService +from src.views.user import UserSchema + +app = Blueprint("user", __name__, url_prefix="/users") + + +@app.route("/") +def list_users(): + """User list view. + --- + get: + tags: + - user + summary: List active users + responses: + 200: + description: Successful operation + content: + application/json: + schema: + type: array + items: UserSchema + """ + service = UserService() + users_schema = UserSchema(many=True) + return users_schema.dump(service.list_all()) + + +@app.route("/", methods=["POST"]) +def create_user(): + """User create view. + --- + post: + tags: + - user + summary: Add a new user + requestBody: + description: Create a new user in the bank + content: + application/json: + schema: CreateUserSchema + required: true + responses: + 201: + description: Successful operation + content: + application/json: + schema: UserSchema + """ + user_schema = UserSchema() + service = UserService() + + try: + user = service.create(user_data=request.json) + except ValidationError as exc: + return exc.messages, HTTPStatus.UNPROCESSABLE_ENTITY + + return user_schema.dump(user), HTTPStatus.CREATED diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/models/__init__.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/__init__.py new file mode 100644 index 000000000..dc252820f --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/__init__.py @@ -0,0 +1,5 @@ +from .account import Account +from .base import db +from .user import User + +__all__ = ["db", "Account", "User"] diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/models/account.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/account.py new file mode 100644 index 000000000..066a3a880 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/account.py @@ -0,0 +1,19 @@ +import sqlalchemy as sa +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import db + + +class Account(db.Model): + __tablename__ = "account" + + id: Mapped[int] = mapped_column(sa.Integer, primary_key=True) + agency: Mapped[str] = mapped_column(sa.String(4)) + account_number: Mapped[str] = mapped_column(sa.String(10), unique=True) + active: Mapped[bool] = mapped_column(sa.Boolean, default=True) + user_id: Mapped[int] = mapped_column(sa.ForeignKey("user.id"), unique=True) + + user: Mapped["User"] = relationship(back_populates="account") # type: ignore # noqa: F821 + + def __repr__(self) -> str: + return f"Account(id={self.id!r}, agency={self.agency!r}, account_number={self.account_number!r})" diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/models/base.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/base.py new file mode 100644 index 000000000..86ff1dade --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/base.py @@ -0,0 +1,9 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass + + +db = SQLAlchemy(model_class=Base) diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/models/user.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/user.py new file mode 100644 index 000000000..f49000a90 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/models/user.py @@ -0,0 +1,19 @@ +import sqlalchemy as sa +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import db + + +class User(db.Model): + __tablename__ = "user" + + id: Mapped[int] = mapped_column(sa.Integer, primary_key=True) + email: Mapped[str] = mapped_column(sa.String, unique=True, nullable=False) + name: Mapped[str] = mapped_column(sa.String, unique=True, nullable=False) + password: Mapped[str] = mapped_column(sa.String, nullable=False) + active: Mapped[bool] = mapped_column(sa.Boolean, default=True) + + account: Mapped["Account"] = relationship(back_populates="user", uselist=False) # type: ignore # noqa: F821 + + def __repr__(self) -> str: + return f"User(id={self.id!r}, email={self.email!r})" diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/services/account.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/services/account.py new file mode 100644 index 000000000..04942e459 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/services/account.py @@ -0,0 +1,18 @@ +from src.models import Account, db +from src.views.account import CreateAccountSchema + + +class AccountService: + def create(self, account_data): + create_account_schema = CreateAccountSchema() + data = create_account_schema.load(account_data) + + account = Account( + agency=data["agency"], + account_number=data["account_number"], + user_id=data["user_id"], + ) + db.session.add(account) + db.session.commit() + + return account diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/services/user.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/services/user.py new file mode 100644 index 000000000..a1f01c9ac --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/services/user.py @@ -0,0 +1,19 @@ +from src.app import bcrypt +from src.models import User, db +from src.views.user import CreateUserSchema + + +class UserService: + def create(self, user_data): + create_user_schema = CreateUserSchema() + data = create_user_schema.load(user_data) + + user = User(name=data["name"], password=bcrypt.generate_password_hash(data["password"]), email=data["email"]) + db.session.add(user) + db.session.commit() + + return user + + def list_all(self): + query = db.select(User).where(User.active.is_(True)) + return db.session.execute(query).scalars() diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/views/account.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/views/account.py new file mode 100644 index 000000000..31010f771 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/views/account.py @@ -0,0 +1,20 @@ +from marshmallow import fields + +from src.app import ma +from src.models.account import Account + + +class AccountSchema(ma.SQLAlchemySchema): + class Meta: + model = Account + + id = ma.auto_field() + agency = ma.auto_field() + account_number = ma.auto_field() + active = ma.auto_field() + + +class CreateAccountSchema(ma.Schema): + agency = fields.String(required=True) + account_number = fields.String(required=True) + user_id = fields.Integer(required=True, strict=True) diff --git a/11 - Desenvolvimento de APIs com Flask/desafio/src/views/user.py b/11 - Desenvolvimento de APIs com Flask/desafio/src/views/user.py new file mode 100644 index 000000000..05c440ac2 --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/desafio/src/views/user.py @@ -0,0 +1,21 @@ +from marshmallow import fields + +from src.app import ma +from src.models.user import User +from src.views.account import AccountSchema + + +class UserSchema(ma.SQLAlchemySchema): + class Meta: + model = User + + id = ma.auto_field() + name = ma.auto_field() + email = ma.auto_field() + account = ma.Nested(AccountSchema) + + +class CreateUserSchema(ma.Schema): + name = fields.String(required=True) + password = fields.String(required=True) + email = fields.Email(required=True) diff --git a/11 - Desenvolvimento de APIs com Flask/dio_bank b/11 - Desenvolvimento de APIs com Flask/dio_bank new file mode 160000 index 000000000..eaceb538f --- /dev/null +++ b/11 - Desenvolvimento de APIs com Flask/dio_bank @@ -0,0 +1 @@ +Subproject commit eaceb538f3a00b5d99bc8d4c772130bc55930c1a From 2f1e61f9d9015aef490be66d6c71cd214da15997 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Sat, 27 Apr 2024 15:31:31 -0300 Subject: [PATCH 11/16] feat: add module 12 files --- .../desafio/cards/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 236 bytes .../cards/__pycache__/admin.cpython-311.pyc | Bin 0 -> 835 bytes .../cards/__pycache__/apps.cpython-311.pyc | Bin 0 -> 640 bytes .../cards/__pycache__/forms.cpython-311.pyc | Bin 0 -> 856 bytes .../cards/__pycache__/models.cpython-311.pyc | Bin 0 -> 2460 bytes .../cards/__pycache__/urls.cpython-311.pyc | Bin 0 -> 690 bytes .../cards/__pycache__/views.cpython-311.pyc | Bin 0 -> 4293 bytes .../desafio/cards/admin.py | 10 + .../desafio/cards/apps.py | 7 + .../desafio/cards/forms.py | 9 + .../desafio/cards/migrations/0001_initial.py | 36 + .../desafio/cards/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2334 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 247 bytes .../desafio/cards/models.py | 34 + .../cards/templates/cards/card_details.html | 15 + .../cards/templates/cards/request_card.html | 43 ++ .../cards/templates/cards/view_requests.html | 20 + .../desafio/cards/urls.py | 10 + .../desafio/cards/views.py | 54 ++ .../desafio/config/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 237 bytes .../__pycache__/settings.cpython-311.pyc | Bin 0 -> 2526 bytes .../config/__pycache__/urls.cpython-311.pyc | Bin 0 -> 1160 bytes .../config/__pycache__/wsgi.cpython-311.pyc | Bin 0 -> 757 bytes .../desafio/config/asgi.py | 16 + .../desafio/config/settings.py | 89 +++ .../desafio/config/urls.py | 12 + .../desafio/config/wsgi.py | 16 + .../desafio/db.sqlite3 | Bin 0 -> 139264 bytes .../desafio/manage.py | 22 + .../desafio/poetry.lock | 78 +++ .../desafio/pyproject.toml | 16 + .../desafio/templates/base.html | 16 + .../desafio/templates/home.html | 17 + .../desafio/templates/registration/login.html | 39 ++ .../mysite/.gitignore | 174 +++++ .../mysite/accounts/__init__.py | 0 .../mysite/accounts/apps.py | 6 + .../mysite/accounts/migrations/__init__.py | 0 .../accounts/templates/accounts/login.html | 10 + .../mysite/accounts/urls.py | 9 + .../mysite/accounts/views.py | 27 + .../mysite/build.sh | 13 + .../mysite/config/__init__.py | 0 .../mysite/config/admin.py | 15 + .../mysite/config/asgi.py | 7 + .../mysite/config/settings.py | 129 ++++ .../mysite/config/urls.py | 9 + .../mysite/config/wsgi.py | 7 + .../mysite/contacts/__init__.py | 0 .../mysite/contacts/apps.py | 6 + .../mysite/contacts/forms.py | 13 + .../contacts/migrations/0001_initial.py | 24 + .../mysite/contacts/migrations/__init__.py | 0 .../mysite/contacts/models.py | 8 + .../contacts/templates/contacts/create.html | 6 + .../contacts/templates/contacts/name.html | 5 + .../mysite/contacts/urls.py | 10 + .../mysite/contacts/views.py | 34 + .../mysite/manage.py | 22 + .../mysite/poetry.lock | 639 ++++++++++++++++++ .../mysite/polls/__init__.py | 0 .../mysite/polls/admin.py | 13 + .../mysite/polls/apps.py | 7 + .../mysite/polls/migrations/0001_initial.py | 51 ++ .../polls/migrations/0002_question_active.py | 17 + ...options_alter_question_options_and_more.py | 52 ++ .../mysite/polls/migrations/__init__.py | 0 .../mysite/polls/models.py | 33 + .../mysite/polls/templates/polls/detail.html | 12 + .../mysite/polls/templates/polls/index.html | 9 + .../mysite/polls/templates/polls/results.html | 9 + .../mysite/polls/urls.py | 11 + .../mysite/polls/views.py | 41 ++ .../mysite/pyproject.toml | 26 + .../mysite/templates/admin/base.html | 12 + .../mysite/tests/__init__.py | 0 .../mysite/tests/contacts/test_forms.py | 40 ++ .../mysite/tests/contacts/test_views.py | 51 ++ .../mysite/tests/polls/test_models.py | 17 + 82 files changed, 2133 insertions(+) create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/__init__.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/admin.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/apps.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/forms.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/models.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/urls.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/views.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/admin.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/apps.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/forms.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/0001_initial.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/models.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/card_details.html create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/request_card.html create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/view_requests.html create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/cards/views.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/__init__.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/settings.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/urls.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/wsgi.cpython-311.pyc create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/asgi.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/settings.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/config/wsgi.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/db.sqlite3 create mode 100755 12 - Desenvolvimento fullstack com Django/desafio/manage.py create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/poetry.lock create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/pyproject.toml create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/templates/base.html create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/templates/home.html create mode 100644 12 - Desenvolvimento fullstack com Django/desafio/templates/registration/login.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/.gitignore create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/apps.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/migrations/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/templates/accounts/login.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/accounts/views.py create mode 100755 12 - Desenvolvimento fullstack com Django/mysite/build.sh create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/admin.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/asgi.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/settings.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/config/wsgi.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/apps.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/forms.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/0001_initial.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/models.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/create.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/name.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/contacts/views.py create mode 100755 12 - Desenvolvimento fullstack com Django/mysite/manage.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/poetry.lock create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/admin.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/apps.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0001_initial.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0002_question_active.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0003_alter_choice_options_alter_question_options_and_more.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/models.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/detail.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/index.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/results.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/urls.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/polls/views.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/pyproject.toml create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/templates/admin/base.html create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/tests/__init__.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_forms.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_views.py create mode 100644 12 - Desenvolvimento fullstack com Django/mysite/tests/polls/test_models.py diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__init__.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/__init__.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad5bc1769906795328ce137d952e329adc7fe53a GIT binary patch literal 236 zcmXv|F$w}P5KI(Bgu})Mq_sGM)}oEI2tMElm%Y1al0A}~VC6G>g7_{!AlO-%fKD+x zyE80v&$2OzFq>(W<}=YB^e5iKC~icO1DVm?gScMY-^u)3d4mRc-6`FGi)!!i2mw8p z3VDfA)tFak1MuKtM&#`!G-&f=FP1E4Tkv4dsL#rP4allaYac{;VkH{3JqlYRm*7Q} p9ttNtFA8N?e_4eM;$z{K@s>?F;R4K(qh= literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/admin.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74db440c11fa63856cf39795422a78b870319845 GIT binary patch literal 835 zcmZWn&1xGl5SI2Yj^l*T*gX_d_G*f~O`*3^C=ImGCKP(xi`cAWdmZh{k#uexoBz^nMXf&gp(a+7zIs%!0Z}whbg#H+%67P;I zM?fwRMHF+C;t*rQEpDZDXk%m{hKEjdh}lz!a|vrD7W#mweTJw5UT$Tb+Ta6gB~$oZ zRtgF|>V8FS@2|16v34VxCK(XteI#jo?+2vHvU~>01!5?~6onSWp-rvOp?2s}2V%K; z-JE?_26DBR73mR^+9?#^TE_BkLLO^Rm@vgYiueO$#_6| z3FB1#LQBYoZhr&=k+Ps)Bz(XmurE>!SuRvS6A{FMCVkQAi7aP9F2N`2jK=vuWF51; z_sZ}1`%JOyRPfUzWmzu#Ucov1XngF)BK7x&QPvj$Wh&~KfKVkBlm+jO#?rii5)4th z0F(PM-}yNCJbUtb@@dw5IXN&xHY@+b_L|2!?=>>)##dJ zeW(hQpnhP?WtUc|cTBu8XbaeF?aivJZ>8zCX}%f*ENqPN4Ea|N=N#=`J)8>ll`6t*w9QhGfQ1tAqt6vhmf2qp%ERCbE@gV24UjEyeK`9MU2c0UQLm0LE4DI!m`JE#72|L*0*U zY<0_Unk@bEtJw2b>QzDtnlnP`H9|~U#VuYZCtYrWI literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/forms.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87e67d00a1fa2e6cc3f4ba131bd391e8452e4163 GIT binary patch literal 856 zcmZuvzi-qq6t?3ez5Y0%v@w_-7bTnUI*eU3Hyq;=P1Wo!aO4%X@zBeSUuS_ilHCfY^99`3C<#Xtd(}Qx*eI zE{Pz5Dw5HNQX(9|l#{WDQR0v{M7W=c@FbnMZSoZ(L%^t11M4QZB z)5daN#YPO9$Oc=Q#dEMPi6jvfBy#GlC~W0iIY1Ayk%$Qf^lsHbrq{r|!iL+REcO8I z*d&WwK}Qsdb0C{7yKVNtu`J?hbF7t+hUal6PkMgU;kC+nwZVC&MX4m{TbzF=V}(JS z8|+!-fm5o=Pg1Fb{XrJE`SN2p)|m`PWva%~fcDnti7d1YMXJN07U@X$6P*__EDXfN z{nPVetn)s)U%U+Z!9ZG>pJ{cLW->2ykd#W;BZoVIG0oJO()Fxn>iMErf2nY_?IWqjpe3 z&Ki7B>>)CV*8m@2<+t!?_eevEy_>uw5IEo#|0 wgkZR&Bl84O@ex=KFlUs~8QJ@_`u$!t!8`XEg~IQ{wEXhv2G?&NaMs2D1!VlkyZ`_I literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/models.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5122a5a6561f48aefc79391d89ebdffb361195c GIT binary patch literal 2460 zcmZuzO-vg{6yEi&fAAl%Axb$ zB&d~q$bmx-+=|pzqH2|-3Wr=Op`I$`lHs`)207 zH{Tn05QzjiXuKy;{7>Y#f0@#5-hXZ~${Y{sU#-;@P&C%kzlK zdk~)&kS8yCIP9(X+7qxJv_M-!0ur~q_rZxr9Y^v#&I~6^hC@E&-wxdO00v&SerMfpM?) zw~u#aCg#kKx4|fZPHdm-%1mDu<`iH~cVYU`06t#ypfmTxe8Qo_Akbl`3v~j$0Tf75 zF(VU{5>p<^FVMgOt|MGGFjFUKU~+|Mt1{A zS@${(UYB(PlL9PFy-eyRW5rF~eDVuXw3I-5w7LXb!v>~df>qhTNUF;<&{i>7)^zM@ z;=ESFyZta1=ozfZcO(_pOGepbw48tPXAKi=_Zal4KdTs~DibO$VuW{rtX#@NSn8?2 zP}Y{ZI_i66*9TpF5@ZnEg7<5>Vy$8B{*x4A!hI+ zT$D}KSnJ6UmWU*-Wx=noY#N$W*KSKPLTjN(6+(BdU<|a4)JL#kl6rx;)^02eR+jsD z5Kz?ow_K%d<9>v)2EBu?bSlKPY;_8{GX*;L;eFH@_>ueB2j?uzI|l$~Z9u@uH6b~Y zOo`N^AS#+r{4~I%a{%!YHYDB2fP@r%MV0SD7^05VqRw1NzW6R(hEqwGOhqkY0=)&I zRj{GyX{2cBf`*imHd54pMrnfpMjlzYYm~M62s2+CgRQ5qj_a$Mx~kNGZCbKusw!+* zs3r?qEjd+@>m@CXur3$bf{Z_D_i7qlxl8>LxJM*uEezqH(F1J|T+`nL(coIWw>IDU z>4MdJ3!W|Xn_>@Nah09yKNH!%C$i0noIR0q$E?7ueU`36w_ZEv`zOKDee0F@yodzI zs`#SEap5{P*kri!5-mZWb?S!HPlt?yOl zODn2LWR(Uqf-q6)rQbP{24Bbs2r9B2$wlZU;nfE~G&;Z5(A!pEpw)kNNe`?(XZ z0BAmSl8nKZutbsfK+xz?b~1Z&Ny>b3Fnn>7Uzgl55XCYV5L-_ z!*>az9Q7|O-pEd8vLwZ{o=jOLGYVFLOAdp4b+fh%oC=jS6`Xe~*NoK?Ps{8D&ARu*hX5I_V+o?~x-| zVe2(G%$M2O^LP`!#{6DX==U|Q?FG5;;QA#ybY?UAFt`2H(+k$u6)RM2hN^a`+L&sE z2AFkVbLJtjLSxO)m>n8};mF{6&W;RjDvv+5BA1(y%XZ{)W2O~9w=rzT&ux9M6Sv~y z&G@(-A8*XHqC@LNJ36$v`uOb5msWJP8J)GGvyH2*#PCMZP7GTY#-GNl#9T8mXD8+w zH(J5d>%(^N^yd2yKeK}AW-x6B(~V4L*Ye|wJ9R6EEL^m6C9AC1xr!C3HX~I#QUy0+ z=Qk2|?EF^sFVTukH)GRwY`QVq8LsUhE0$@-GIlJ}nB5b3?-%@L-yX-luaQ2F_su=- zsCdT4p1;Q6o&-CD`IL{IMou}87T`i4%JL}OhviYXnq4;a901U=OE>Rz_Fss5h=&jM zMPS^moe#-1Se+HAE`it+c%E-@?^uVQ7I(%v{OtJ#d5GnxSikiB_%k;C{53`=p8o-z C!G|9J literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/urls.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c818f5a27138e79f7c222908c6cebc7d99a8dbc8 GIT binary patch literal 690 zcmZutF>ljA6uyfujZ?=06&OIMcwvaBdjhEps01m=Tp?x45?Q%R;$ZvSyK_*|p&6N3 z=r4dG82S_V36A8U@)U`wTcvKDxHIuk#Pj*|zW2WO-FxqwZEpi*=cf-xuOR@xH7KpN zac({<=L;Z!zyO4>4*H< zd1C_y8tTf(3+bW}&fQHy;M~>%gAy-xO9euu=LZ5G`jLF2>jyq*Dapd!B#)Z)+#*vi z8ngX`2ZHPAbHMdla?^|Bfqszd>3CgVdi%MhsA>dBc_hAr8x;b#CwRg_ipPl`Oek00 zd(Ng*GJ%QD@Q@LI%-j)+B*l^|j_1a6Ibo5j(=T5+uG6K0Ml%-7{E$YHIin;9g!G0V zoFNOHZqXzrRCpsTsJDf6%lq;C);tUuNdns9JE|AGSaGVZYXd@f37)2R&l)^P@1C`_ zvozPpTy{S_JA0lz*}G_^sJ%k%47D$d@b1~8?D5{kyA*X+sFR^i5l-vP^MmZg+h0d1 W>a9>OL%r)7x>s3R`s4Ri4}So{(87@b literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/views.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e9b77f8e682b5d8db57977a312c6bd1b5c564ad GIT binary patch literal 4293 zcmcgvZ)_9E6`x(N|0H()fKB3S5^SJcFys$}gd7k!I3R!;IR5qEqE@c)Y@7{iFE_gm z5IHESs2Wa4M^USw-bJ-ecVGmS@}VE7)Jj*Wt?r{YS_x|gR~aQSUAC0OosKeG3^4 zK(FtO-UjHcd!x6J)}JRa{}PUlab}~eq@+xoiJxU9CeoP8T$d8HxN>$gLk)a`$5YS5~U&7T- ztvh=1uZ@4*IwcsT@xsPDgs>UEnCNfmYXpdYb-?P)?;s9*uQPL{nJsO>Po$DP} z(qeBaE6L*`1MUn{CPYn9d#R-KCKW2Bl>kx41NR8$jVI3Yx4wRchyucJwvjROvviNo|Kpo8Q9+Hj!T&_Wkt3!8f~lg0{Qn0DQ+Ao*QaOa z)%>1^2fseJuxa6}zGcwZGN?yBFd`q=Dz9J&S!xDia-W#osn=ZZ|J!eD1H7HJBKZN3 zs%O|n?7@KbzgeaQ=d4<$?L^V4PdCjU$$K6)e%)C3Y+;ig?Kh(Rdi#LUK2VkeWl3!~eOUxlWkl_o!mgN2;`OT%I7d=4aXcTUD>*qC^D_+CtO-dYk}wjy z4@~#em?X=lE2CU9g&SgmnZkr3WlT4qG6gS4jw@19jJeC>a0b(tOvclIzj4zG9FDQ3 zSMef^LOEm^%3S{@PNuf~w}`@vOXc9~0eVWl4YLjSEBUq;*QzgrEq5p9Zr;0@PZZYc ztzAZImmcgkg5A%8yBCAI7Y^&eJw|ZPtnc~y4R^=q&QeQwE_E+u0~46PZT+7AOYE;PVD*h72GEvMM$4&EYscL5z3F_i zusc7hx9&7rca}D9FGlu2EnF-=OTi6ugZBn)X9Dx*?00AXc>Pb;fz{hi7(jz3jo`^; zzo)r=nUqOgz2$XNIP>xO3&W5}dEjWKC5?50f!H=AF(liO>;N(qv<~z>JkVAW2j^Fv zYAv5~hxNy`v}_7vn4mSA0+o`Q1q;)Y79pHahEa zSHKVl3^N>t=@DQRGD9nluzu_u1Y*|Vf~?qK1}wIsVuL7J22r4P$?Ome#bv3@fQ%Wk zECk&3-wNNJ|NWNyrEeOS2{c!ecH(Qgb@P79qBm0XMxHmd-uHeL(3`dzOVB7rh;t%)g?fZ=OeR^=e5!_!4>|cHlmiDi9(j2}$b!Tc; zc%ei3 zh1mv#pN;L9hv9{9ddpuZs_B7%rU5>2O4Fw>_&SMSzEQ>^&kjH@`v{1N`;Jy%*5G&z z8wOD~>Y(Bbc^L}-dgNKCb1~FexcKBJ#Zadn+Gm9JJqsOK3?2FIlpZ=~gwD;JsyJrg zY18)|#lefZ|B~UqR1_|iJyUCE+cQ6d7l3DMZ#chwa*nSX@B{c{B`=2T+5V2OEX-5* zCRxM6!fD8vc!`#+T!{tNsiUHnkYu%NR{^_X0>kXsvZ9bSn2^ z^`zEL$bJO!-Kc~299YobK7S#fS~&iPk)r>w?muk!56>KZ7m!cKn#_&VN>zH2aM>{F za!(?wjrUM7slWxNFqI)f&E|@dI<7D+nbp+t`mG>@W;Xp5g)Ea*D=e9A40@^+;2_#5 zR0IKH$citP^R4wiB9`NCOC(Zs z))%C$=&U6YE;{S7hw#nIe3TpJmI=~-x}f9M#!A;PXNh_4QxGGy#a35+MT&#@+QHZ; Kca^EN=l=lB0nR)C literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/admin.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/admin.py new file mode 100644 index 000000000..b6f99b8f0 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from cards.models import Card + + +@admin.register(Card) +class CardAdmin(admin.ModelAdmin): + list_display = ("number", "user", "network", "status", "created_at") + list_filter = ("status", "network", "created_at") + search_fields = ("user__username", "status") diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/apps.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/apps.py new file mode 100644 index 000000000..1fb6260ef --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class CardsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "cards" + verbose_name = "Cartão" diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/forms.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/forms.py new file mode 100644 index 000000000..5771c141b --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/forms.py @@ -0,0 +1,9 @@ +from django import forms + +from .models import Card + + +class CardForm(forms.ModelForm): + class Meta: + model = Card + fields = ["holder_name"] diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/0001_initial.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/0001_initial.py new file mode 100644 index 000000000..ed3384bc5 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.4 on 2024-04-08 19:28 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Card', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=20)), + ('number', models.CharField(max_length=16)), + ('holder_name', models.CharField(max_length=20)), + ('network', models.CharField(choices=[('V', 'Visa'), ('M', 'Mastercard')], max_length=1)), + ('expiration_date', models.CharField(max_length=5)), + ('cvv', models.CharField(max_length=4)), + ('status', models.CharField(choices=[('P', 'Pendente'), ('A', 'Aprovado'), ('E', 'Enviado'), ('R', 'Recebido')], default='P', max_length=1)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cards', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__init__.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/0001_initial.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f0e50c25efb697cb3bb6017cd76a19befb47873 GIT binary patch literal 2334 zcmZuyJ#Z686uvvj(&@*xWCXI1K!PX}%V5g{>=2R(Shi&T3?5@VK?!qn)~=)zr#rEC z5|}0wDN<$V;*v?>B!deVDJUsZWmYp+Xtu~OlggbKsHn1gCprFG-M-!T_S^T~zJ2@j z+u)#|qwQ%vQvPRvw?**vjEq41b)|x2Q_(ep zcfp*7uo%o;~(?YM*weRrsQ4V|{%I5CGfl zuLX7`Jm7&F3~YHe-E=SQPAdz*Ya@ew2@tn@oBQ-rjQ;>8a0DiJ0CSX{!2_5fFkBHp zXe+!)PydScU9UzCAdekkF>(NN{0Pj6{g?^|qQCNBl-i8#zeRe=lQwp+?*-tu;)f!U zID|QM2y@!T4E6QE;aaFaui4qjxLSBuHqxJgm`j9Ve@IT-;WD4;-u8}k`A3Cc}Vmr4lACIlqVKuTRk>(V2*PJ9MLimWQn zAPKf0TGI_EX>uJBH={cd>Lkr=e_ehmsZgt!RpQZ_^)-lg*y%{1s;dAZXNy;Z<`W$~ zq7Q&yCGJ(lklA#R_!nivgs4Q_+F=PyrQT9iS4z-8EFfeUK5Zz@gOLDra)&(t;wrUT zJ1l95$1r8HX=L0?-x3j*pa!63LdMP#akhcp=7~42wUi#WOvGhaf@=!U(HUmh zh2l)17r?UIR87aczZV)wHtAuVXleQrNd|!Un+@g>aMnaSl5~x@n>2D{fSt+6xQI{J zB>K#uN#ls1YVWY)D404CbwqGs*uiulJvMKt4uHDyo#on^zM)yqKNTCZZzaI3IwoW$EUQ8Sh*s@v-el7OZV^P zm!-wK^ZDB(xQkGrK>~AjVl1*Uh3vowb4q2Frc8l3MtpNs89CFzd3vxFr4Aj|U(gX$ zD%vfYNFGp=wTeyzutr2X614ge@6z(!mHgZa@#_uPtB8Ma@2OYZ$DgxRy$-XLrlM9M zqO&EW*Py8zS)l0Ik`9!LK2g@`HD^skG4e!X-K^@`1Y5rFd1@jx4-KfbbhSl2(o8*7 zZmKHHl+vSANw25oZHKY|8giK_WH*)lzc6Q~rlu}Py%_?sr`^WERu zp^@hcT@U9Q`^j&KpZzXcW7pc^tR>ENT|&h3{C3yR`I2nybSE5r8$OPs<1boPbo{mZ zuQV=7SVK5!w4;UTwgVwZ8~%T73kL$2weBbz`Azmi*ZUWo5lPHR*GaH8 z>HI%W_H!2HD2xPp>m^+)JFm#MyTg8P_DgYcW`XIomEWMVkK)(PgZ&0AQkK;_!=$9^ s;(4B?5Pt@9Dd+3-CwnAgm^+%N-^!{8lLajxBsRV%7HQKaKW&O8@`> literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/__init__.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2039d96765e8de297621f8d3b93f1a07b4ba2c23 GIT binary patch literal 247 zcmXv|K?=e!5KI(BgyO{q(b{f`h5UZu~eiG@`r str: + return f"Cartão {self.id} - {self.user.username} - {self.get_status_display()}" + + class Meta: + verbose_name_plural = "Cartões" + ordering = ["-created_at"] diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/card_details.html b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/card_details.html new file mode 100644 index 000000000..b4fe7cbf3 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/card_details.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block content %} +

Detalhes da Solicitação do Cartão

+
+
+
{{ card.name }}
+
**** **** **** {{ card.number|slice:"12:" }}
+

{{ card.holder_name }}

+ {{ card.get_network_display }} + {{ card.get_status_display }} +
+
+Voltar às solicitações +{% endblock %} diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/request_card.html b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/request_card.html new file mode 100644 index 000000000..6d7fe6a49 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/request_card.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} + +{% block title %}Solicitar novo cartão{% endblock %} + +{% load widget_tweaks %} + +{% block content %} +

Voltar

+

Solicitar Novo Cartão

+
+
+ {% csrf_token %} + + {% if form.non_field_errors %} + + {% endif %} + + {% for field in form %} +
+ + {% if field.errors %} + {% render_field field class="form-control is-invalid" %} +
+ {{ field.errors|first }} +
+ {% else %} + {% render_field field class="form-control" %} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% endif %} +
+ {% endfor %} + +
+
+{% endblock %} + + diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/view_requests.html b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/view_requests.html new file mode 100644 index 000000000..c1f502712 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/templates/cards/view_requests.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block content %} +

+ Início | + Solicitar cartão +

+

Minhas Solicitações de Cartão

+{% if user_requests %} + +{% else %} +

Você não tem solicitações de cartão.

+{% endif %} +{% endblock %} diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/urls.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/urls.py new file mode 100644 index 000000000..8b9801aed --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +app_name = "cards" +urlpatterns = [ + path("request-card/", views.request_card, name="request_card"), + path("my-requests/", views.view_requests, name="view_requests"), + path("request-details//", views.card_details, name="card_details"), +] diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/views.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/views.py new file mode 100644 index 000000000..18f3471bf --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/views.py @@ -0,0 +1,54 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse + +from .forms import CardForm +from .models import Card + + +@login_required +def request_card(request): + def generate_card_info() -> dict[str, str]: + import random + from datetime import UTC, datetime + + cc_month = str(random.randint(1, 12)).zfill(2) + cc_year = str(datetime.now(UTC).year + 10)[2:] + return { + "name": "DIO Bank Platinum", + "number": "".join([str(random.randint(0, 9)) for _ in range(16)]), + "network": random.choice(["V", "M"]), + "expiration_date": f"{cc_month}/{cc_year}", + "cvv": "".join([str(random.randint(0, 9)) for _ in range(3)]), + } + + if request.method == "POST": + form = CardForm(request.POST) + if form.is_valid(): + card_info = generate_card_info() + + card_request = form.save(commit=False) + card_request.user = request.user + card_request.name = card_info["name"] + card_request.number = card_info["number"] + card_request.network = card_info["network"] + card_request.expiration_date = card_info["expiration_date"] + card_request.cvv = card_info["cvv"] + card_request.save() + + return redirect(reverse("cards:view_requests")) + else: + form = CardForm() + return render(request, "cards/request_card.html", {"form": form}) + + +@login_required +def view_requests(request): + user_requests = Card.objects.filter(user=request.user).order_by("-created_at") + return render(request, "cards/view_requests.html", {"user_requests": user_requests}) + + +@login_required +def card_details(request, card_id): + card = get_object_or_404(Card, id=card_id, user=request.user) + return render(request, "cards/card_details.html", {"card": card}) diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/__init__.py b/12 - Desenvolvimento fullstack com Django/desafio/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/__init__.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae2c3a1f99133d78a857f42e2434e4ba2a7a89b9 GIT binary patch literal 237 zcmXv|K?=e!5KI(Bl;Xt)9MCUL& zyE80vOOi1Om}D~<=OfY|^e0}!Ag)A^J(<((o!DO3U(tMDc#SRas#CfK8`REX10Ee0 z3VDe_RhUXN9=Nw5Q>4eUuhFE@UanZmHsHXVP@hx_#v_xR*3K8@ftA>@O;eZ(xd2zl q??WS1#q(Sl<@0=TJikZl7DcBavx875R(8`s&pxH}N&aN};r0RH2|&95 literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/settings.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4459f2352d653de2973e870c516e2dc325d08339 GIT binary patch literal 2526 zcma)8y>Ht_6esolVac*AeX%3) z#K3s>XQKXFfMNcjlhN=EnYSOa4D&OC7{qFfM4v8=ZMq~E%eWZq!tPg~e+~Cwyzq>; z@#ia;ygnA}ct`9BC;2e5;~&XiKsKibawE_0-d8S&1@~Y`LFAJ{4Mqw#803G+N>lI* zz%zn^QWS--4~6Y8!!%grLsS=e$*L@hKKC?OX&OySF%&~HU=^oUX!f~FWu+OEKyzS~ zK=Te=aOk4SHat=sos(wKc_{(w&cQv8mZSxAL0Uu?rE_qfN36&sFa1l?D?MBm;H=?I^8=&eimmWvE-6f|_c}o}H9-tZjQ~ zLP%Y3%d+;{*t8cWlqNP!Rd1bEG(n*>z~lU+hox9*t*&a=B#}K8HLxXHdsx{u?fIHw zAd^$tZIjDTHlw%dY9r~lQ^O!l6(J4pDF)_Xn;k>7_BnCLY#i(C+mmY!v*gD7#ercEiU6c{>Rib>RblM0~PO$nSM5rivk#r=fT4q;8kk?i{2V@oZQ3+(Q)UE z0m@@upMGEynp)f4QEIzttHFJ}W++WuY}3WeF~2YzGhoei49&FD!yp!Jwl&4V+!lD_ z7M#sck#(x6GE|!+II=WJ+^LuEEV*syHAqi4%%sP@HW~4>6^HOvr(s_|gQbBVb+BpK zH-@$}m{Zjh+YdyhnA^7|jF0jBfWt_a^br3lpSdp-vc!|kl|>TZOC{Oi{KZlwS1gG3 z0tBGY?U@ag7B9f;5P(gnmXT` z$k{2Lwqlxlx`E`!il!oGDXwZ_gSRLM9Se(UQ`Hpuay%GV=!U&=)<_#_OKogBlL(*B7dM5he6J{0L=w#v#0rQ& z0OcZ*&_*ts%?q1+Ss+v8VzDAu%lS;PuttIvVWX7iD}qR3o8o$o+UIi_o>mqKX88(F zR~N}5U#;AeOS~v<7Ry=r0iVx-vIqqk$@7KvD!(qsnPOIefVmApmWohEfhy#kyZL#6 z_$XJA;5$))$QimiiD!j1zM8Md5VoAk(08z6(tzhMua^??)O+Q`=Tj?R{%oZ>6aE<|oUk zr_H1hHkG;)kiKVW6I&LvGLviXzYE-jbM9z( z#YjOy^nZf+EzrK3W!WPp*mZtK%;LfOS1&yM4tiNsjjydi+3Y^PcZK4Gb7TC_U8jp5R6DS z)K4(6i`}VyfJq4K(agDL_YY^T9>lI4B^G;A%YDXsB|1jj{zRxBobvkm3?=Sp#MKS< zmzhNBfQj{ej5m4|=Z<2F&+Z<^uJ*m|7#LFG_Bw`fCOCZ%llpEKI0u|bX?*h4_@q3T z%tbD^0G0kFW@fP)=}mpq4FW2#_h6*!>qX{ayK}b>n0Rk)iPHHC2h8l?GHw9O{{dJO BJQDx_ literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/urls.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b88fa96a8db6e208f88644d3b862b38c27b972c1 GIT binary patch literal 1160 zcma)4J8Roe6uz>rBtN1gjXX3^ibE)^2U|Fy4J4)wB$Fwn(9*#KQdRfbmaB)6D9toMgHE#y;Br;3x9M4U5Icc@T#tczkp;F-&qbmuCs1w_C zn6hKi4}K|?$fjdhJwg-Q)q^%fre0IKYv}=f1BkxF6STf$xh>Pt5|Q5k!;Ws#z6_?@ z3$%pAK}k?Q+jebQX$Q6iC?&Wi+h*_tvshm;bdUI2KgaJ%&$IkQSBM)22?~ z7MnfG@&ny?Uo>31xXA~i5$fwrPUtkvmd47hba$^ZHQOaUi$3-4Low_(^8dhbkro7D zh>GFl8lvTJat+ag@U}HXOX1|YkdT<&+uXl<@c3}`@L7ZxW4t)T8SYw*)raS*cBX2P zT8-7}sT|_<2(QO@eFT|5sKwauE)4Oe$~ps%IDh2N~o?y>RJpZuEn@^k(XvtdwKSF_h0~j0Z5BH A00000 literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/wsgi.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7866b7a392996b946ecc025ea0fe52cb72a3f995 GIT binary patch literal 757 zcmY*X&rcLF6n>orRtH&)0nQEKLgLPVi5Fu?Ad3W}V6qs^US{j`&1|vLPNv;OhzAb( zH+c1gG5j-&F`CSY#FIA?Zk+7wx(UAidhL6C@9X#ab#BfFC$sDG$tMEvyV^`->YsAe zbINDn&;d@kCrO96wI;o+)~OLdaxn3PFVmCDH&;Hzr%yeAGdIRUzyqwEy3N_-2s$&| zYs1RS@lXGCdwYk*S|uW-i7qB;Ug!giZQ%Pmmg4(d8#L7RkdFPClZCdD%3wA*EQE=A0&(5l;Y@t(RQnJ$nD}C1J%j}7ltN6;sa_B{hqbC zc^rmZ$0pzhOr?5)Db`ttDtr{IhCO{~b;vQ7`Y1zX!$Xsb(En4tba#c6^C{Y>I*V?= zC>LALHg>o7qW0GQ{?6`pJ9@F#di8wk`0kj8z@W84rDmmG)}zQFh@#Tdro1=C5yTFD zP(>|jE>BrUH4ueXrSC3sOjuvqa`r~TZ*Wxsy8htsW*DY@A$wRj?PbNX)h6UZhq2}& z)y+gJi=i!?4{PQ}wx?CIGOsQTvVXKZ&N;sJ zy?*3DI;(xmXU&}W-t(RB`_Av2bM5n;bA1=*r_yC3u$C`o^>RRPoNzFVM`&fDlL(_95E*~<&wk=?9tZ#dAOHk_01yBIKmZ5; z0U!VbfB+D<_XPUSda=^ey^lE17tn`M5?%EDo$rIbVefx?KjYo;ihZy4eWdS3-^t!D z^}g7f00iE~1ccL_j`LR{C8JbI=W`{NWw}^7moBIE4A1gn zkj4Mh0H=j{EzD{mK~s5EWlwcF{8u6gy_h^g35nyCv6G#S{*{PcDX(J-d`!=jjbf}) z!pT~?SSrVIde(?#_1m$GkxP}=+uBxDffJrSfi;cRG;y&?A*q**SfQA|y&EeS#caA- zllB_$S#|_r4bwFZtm#Xtn6DI$t3vAv8G71 zDDx@kgmj3T~3fySsHU=CAH4W z);h1%*N)U{1Xd8mv93xnWR&9SwNo{A;b^XbGsjm@zNuSHx~6{c*e;w@FCTX@vwiQ2Z_u#(7X ztdm|4YAO!Agz%awu^tC@`C0?zwB_Mc_;c7 znn#~O+vo!*g+BT=zNACdfB+Bx0zd!=00AHX1b_e#00KY&2;2(-XF8lgCb^;KQY0#X z;}0ZYfV@4<3k-?x(WsvDw97eEJ+$H^=e!>0#cnD?qiyKR4qt;qzFy}=hDH@*Bz60p zPN8PWocPn&h#QycZzxF)`WffdnshduD(WN#Qaas(v)t%C4)h)LMf6qlS@a(CYv=f00e*l5C8%|00;m9AaG0qK9|40I-#fY`7^HbXPMeoBlB<9IoFj- zjD1<(_P*2W>hGsv1hU`5<#YA>=@AVd;21;yMS5t37&=b6{O3J1DpA`ILH7Tj>;0w! zeI5Ni`Y9AgVbtgQhVK);5BrL~8DGeE%KIPQzw&;}`=WQrd#Ufg`o7%vJAE(o{Y2lj zzJWdmE(k9m00e*l5C8%|00;m9AOHm3LvS*|9&>M}jUBphIHKnQw%VTtos+++`ZO#yyS^9CNoc~^p6 z)VevTsXJgEKkCOagIt&CgH6hGqbB!2RnGLpCTBWho(c40xl2P`#2=dwamhR!I)f#G zN@tacwQ0KUnK{b}kiF(t@l7BM_m0jQqDJu=sp{Ek0ek}pDNrNUSfB>ZR$vg{|G^AN zofKfBH05X$J%?}bz^&<`o|9=`-DPqRz&CH;n!8L_Yx$;+R07|bK_pDyY7(YXAJ`fqxgSw3A z0zE#5Z*9P}bP^xx0>lY=@HoD0ff*7ys2A4kw(g2`>IpZNysC7V{#Y4A)^y4``2@cI zfJmF3Ss6sybk92ZD89!4_u3BPqji>u5=X5wm+=h*p2s-qY;D$EcNL!q&>IsT$M*<$ zh9uKbyPWB!C5i71z>=nuc1hF4y5s=9DZq2(Dsj**M%=5*koEs_-mg2*ck$}~=Wz_+ zSI~3zbGCZMoJx|=&BHzybDpV3avnP6 zVy-X^!J7xqxR^nvDOTgJo-`GiL6!%oc6$`2{{a^hWLlywXC8Dh9MdMga=MyP^UsQm zoa%QmLrinLy%3NUn@a(NzO8lnlVGcL~%(-b;z z(vzmn+VsEUgv)aUZve0Y0%ZN)>HP}_n)7`S|A7|}00KY&2mk>f00e*l5C8%|00_JV z2<$!O^g8yQigcep&vko*3x)V*a*dCb)^%QzFDpF$ML72I^jsu=-58z{C+3oTF`B-W z7+#ZyFK32|W%2gBmQSqAtHoJ)xmXdmmN;o~m!A+8Zlxxdq^sJ-&ck~gxh<{sgJzqP1Iz@g?FqFt=o63q|o{i-6K$Mi)i*!`| zzjR*M*Z&)3ZMDP>=O$O?hbt*o9KRWff00e*l5C8%|00;m9AYdcV z=RD8U;xA6*yu{do8y($FfAx0&$ojwIgsm971p+_-2mk>f00e*l5C8%|00;m9AOHm3 zb_B@$-`)4q4z!1yzUO^T?+acJK7f00e*l5C8%|;PoT$UU#44YLG+z z(%&8*BbCM_tlB~>E^vqTT z|KeUQpEaiO6Waw*mif6R14393L#oJ&61QKDqINvjNlahe&oth=FRS<&?;IHM~y!<*)OG_OB!C|6kcW} zrPcloji=6OkrG}e)m z!<-b-Fn+$N_PiM8_z=r!k|tH3|M#*3{Se;)@So`4&_APpKrg?3!yGgN0U!VbfB+Bx z0zd!=00AHX1b_e#00Q@rK&O*ooNkhKbkVevrY@Q~T}}_v;dFXDu>b!aG6z*a00;m9 zAOHk_01yBIKmZ5;0U!Vb-lPOz{{JRz7;p;+00AHX1b_e#00KY&2mk>f00e-*JtRQ( z|DQw8RQLaX4}BZGivAIO4SfZD3H=#*34IoQ3jH4XDEdwG5%f#w7tqh3_u*d(cotPq z9;HzN7laoO00KY&2mk>f00e*l5C8%|00;nq`<(#UYwtcs)3Y>vn5GYrwCh2d`f2(A zP5VjOd4{H^X?lvLCrRo$K~qFiA5Fa^b@tJ;m!>^5^^mlqo2CtS0FYh(E|S3h|NFgb zLj8dN5C8%|00;m9AOHk_01yBIK;Zr&K-T}^`G5CUn?r4Z01yBIKmZ5;0U!VbfB+Bx z0zlyYBLMUN`>)NR#y|iF00AHX1b_e#00KY&2mk>faQ_j2`Tzab=1^lG00e*l5C8%| z00;m9AOHk_01&wU2*CXR{%donF%SR(KmZ5;0U!VbfB+Bx0zd!=+k z00AHX1b_e#00KY&2mk>f00izo0$%s`9f-IUf9&1qdw06_{HX zpN=f71}3AcfynaG?Dz~;G##B;dLl58k8c=>a*UKK8@J1}fOWcP6p90ZZM~RS*Nczy zEIU}&iiPwNCpGfh};6@ zHa;`HG#;6nS~X8buVE{dl2L5bUV~@1pjXQ4F(P_oy=Ilm=`5~VQZF0iU7gI}-h|)n z&qtWOt#mGF+^*}VMq&k{m`#^Tm~qMW9=jRi*5Yw#jZf@f;9;|`jL%$)-o#B+^Zclq z2LiJ*2Nfd(EdL0)!M%5$a{D)f%wDOfGO1#|QYcj|W9R81sf8vA5}RNXu}UtzWqDW? zKW$cEd1icmIT~oG$B~pa*P>d8_+D#p-1aqpa1FvI-Tsuu?CDkws<~EGo0~!v6H-Fs z4eEc3@ToS<&?3~-xHXM7=Ua-j!#{Mw?SBS4KYft%RZSRvoa5OrGsrLWcnT1)+CW4>O=BPgb_ruT;IvOqBtfOg&H)z7+{PY>Oe;!|{q^63~<|x^&P>LjJk|;L!w+Gq`59-2x z3Yz_`$`LQ=J9_=RJJapitmHBtuVfajO?Ji$1hpe`pqL(b?QF znhV%F^5EWy({BGP?zdc%>$H`uPgq&!cq6IW9IgtS+0V^=$~QY#6>D^91L2E^Q)BL4 zvV5X)wu^;rL|KhU#x$Eq3cM6&tt&`N(G#Z6ZN3Mz$XWyU05i5qwJ0D}tDeMNvZap> z-o4r9_D3Sj3-7Q-C~dIjR@=X)c-xHBwkpg%-a0N0`ZiBB9c?rbQPajDA3QA_T77Ey zz_j=u&BowdN0)zSh*_mzOIw+A*@%^lt%{LL7)^)H282zSkLUEP@dPe8xVOr<{a3Ft zcXg`MoGoS3siID%Ev1$d9SwSG(<`gMv06kg6l@bHNotrt5nuPu#YHn|178|j=MAv_ z-+En#Ge7_c00AHX1b_e#00KY&2mk>f00iD{1iU@3;>Z6b9X(Hae!KgX?o(a=idX-$ z9j`e*;QU<2pE$nZNTMF}lCN}}dSlhyq}*-R?H{?w>^(%|dI`OlEX7D#4`FHvP7wt` zv$A^42#UE_XJxdMsRrRJ0TNrO1+NC5Ph55TFJ5HccalbKEGg?gm;-*bUg9_tzAzckh#Ym{f5^IbWCAPP% zEU`gDsjQbPB`cR}$W0UttTq|b%WYywIJ#FU5VHs5zaER@gIuV~AHBdhX!kOscsOpF zr^hiXg38t3cJG}Wq8(!|YsFt_xR#30cc`&rSQ+=AV{nqKX9TJN=|SO%F8}Do1FKPu zC|B!2-(PtC$AWJEg$vA2Ua~4ex_`Z@?l#r1MQmFmum?qj3wo)9`yY-Z*84E8HuR8; zUMiCvC+VEMhu{qsxOb#Wu~G%cbF1d#`Fy6ne}VKJ>~&+`Z$}9WQWFPAVi6;&r!$t5 zywupqi6dowZEYaX>;%ps8!fi+&}q&hJv+96J5}32DmJ>7F52X1kbZZU9D%E8=-DdH{5;~&lBi95sO3gXEPvA^ z_MWA!-^P&`UQdW+^th3UWpUeQVu~D33P~YO8@^2eSKG#KBY3dpYP+{7M#$8CBCBoG zE!>gEA94Eyfw`;ECT~-+&8hQ^P2Wc1Sj}(ww{NRdy>6e4S8UJQS8PqU>O1`E--{BG zEE;iLZWcIaR?GZG)tuc@z5#aK45>P~P}6M&dyk)U`(tawk)S&SzP)rdFS zK6{93_CY|diwRTD{w_9^7#MUooF6>SS6C;Lz*bAvV@00e*l5C8%|00;m9AOHk_01yBIZ$<(z|9>;K3it#BfB+Bx0zd!=00AHX z1b_e#00KbZ-V%WM|Gni2+JFEM00KY&2mk>f00e*l5C8%|00_Jp36S|eLcire-$&m? ze}w)3{Q!Ln{S$fx{XO~{^hNY}^l|jNI2&F-00;m9AOHk_01yBIKmZ5;0U!VbfWYfd zpwr1PZbVZbO}!*__0hDKp6Q{fho;>$b7N*UO)f{00AHX1b_e#00KY&2mk>f00e-*y&>RryBz+0`oKAoobGZt&Yv}( z1W+>np6zrwu3R!7XHwoR7$w{LK9|eU-%p-gL>|fFb>bsGIigPnJJo>?FOoy+xkV=# zZ1FsOKns3Emys(Mcgg%8c|Pqx+eq?#+BfNa#hdH<-@c#g8|eLbZ>8tIdw#y>QJf7g zAOHk_01yBIKmZ5;fj6AM2J$%2B(uVJBFczfEWh;Y`G67#2K1b6WYTwZ{C2@e81Zy6 z-|?*pHymOk|q51T?32N=lwqO+;h*kd6pN0tQh3j04s!fCCsWJ zRuMUkPy4W~@dIr|bK7bCM=-^OG*u84e#3icwIYRLe*4(A_))j@gD838Y(P&Yi}*Qa0|ck538xqqMO;T-6x1hs zaUIWlhU{i>6XDX;25MWHXG5y0YLc4m!KLSEV~Dke7jbK+Drr2A*w!=a`Qx`etI@Vs z!<-W06jtD+j0c;WJGl*%2m2h`26N=V{lkrlG;qnMjBa`+Lq zW#fRZ(QX`B=7bQ(^D@h2yN+V+n8VgIhled65+q4b4CVDB^dWc<^4T4qo2Y_YY{K^b#GvcFX$> z;kp7|3~`#OisGJ|!OlmSy?%TN;A(@IBUkTaHl4$NkAUeKepp~2X#}bdeSdd_WetU< zLY_PWw{4H4RpG@j$A?&+s^1{4T1q600e*l5C8%|00;m9AaEZLxO=I)bNcz^2w#@A#dJZH zch@B)Eeho#7vIh+*?4xlxOE3_xG*Me-`JQg&F-Wl^9yUi>*dAh^aeMPs8m#8b3A&h zu#!(r8p337Hl`OM+esleH@mi)9p|}Xn#*M;rp4@?$k>=HEKFsGwM3E~7K+70E+H3- z(~E{YIX9ZVxx70QyqQw)3h{7iO4Ic9TT8bl3bVUoI~&v4>FaWGdSi5DDY!eoDGaYh z)<;XiYIJ^ndS-Pp7#*1|D&oX#ac$??#@L-?G0YV!vwAr^8QaZY%Z!!Ad1a|w9@P@! zc+gmjrbZHr{K)LB*$sAQX*M;Hot_hG8zXp;?lq9Y5*Nb0D2jUbQWvF^)l^BXY{(hB z^`w-|@_8krZWi*zd`?Sja_RhAk`k}tttXY4+>O{oE}Awr*LQ9g#p&(EJ9E-xRNBz4 z>no8`kqzFwJ}Q-OMe39UN=Xh2YDiQSya0XoQU|3}EUd{|wz$bF>2hwZxL(SxtBR1# zsBE#k#j=UFCMA9%JfbbE^V>?iaO=jjc6)wda%TR<&86F`tK+v<6`4%u z^Q9(AY*^6no+UwGdB@!WH^#IW$s~8VObK_W&9!WLeTP%C+iGfOtFj#z*EY5*MjK3H zBg?~LMOaJ3N5<7#TMLEp#bRkUR;(;0C&%ImRo140@oU@bE3qVZN82oCuSb@)wbJ^= zVkB6a4=yNM#kH|zxwu-{Tn$FYl=z*>$_@2qKE^BZNc3iDRo_gl=O%T%G&P!?xUrGC zHoY9pUYD157AvXk^3wcpWPU5hjt`HHr{{Ae_RjF)Ts#<=*@(>qr>i6GS~xN{uCN7V zUEe8`Cgk9qt(dVP3$e<4Fs9$WmdecQ8>0(zm95gaMX5I8SS`fM5+}+%=<5y_8grmA z^dXc*pSlk&#ZW~c00e*l5C8%|00;m9AOHk_01yBI$0l&uIn5jv+v_GmhlRGfh|pmn z3pWuuEd0}fD|7j<3iY6G;`#rY1HFV^M8ARGzzYZf0U!VbfB+Bx0zd!=00AHX1b_e# xI3|HdoDrtJQ=zs7U*Gl8-H_d~-J`1^yH>isg70p~u9miKgCJ3WNA`F7{{W7?OS}L8 literal 0 HcmV?d00001 diff --git a/12 - Desenvolvimento fullstack com Django/desafio/manage.py b/12 - Desenvolvimento fullstack com Django/desafio/manage.py new file mode 100755 index 000000000..8e7ac79b9 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.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() diff --git a/12 - Desenvolvimento fullstack com Django/desafio/poetry.lock b/12 - Desenvolvimento fullstack com Django/desafio/poetry.lock new file mode 100644 index 000000000..06ecb33b3 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/poetry.lock @@ -0,0 +1,78 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.8.1" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, +] + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "django" +version = "5.0.4" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.10" +files = [ + {file = "Django-5.0.4-py3-none-any.whl", hash = "sha256:916423499d75d62da7aa038d19aef23d23498d8df229775eb0a6309ee1013775"}, + {file = "Django-5.0.4.tar.gz", hash = "sha256:4bd01a8c830bb77a8a3b0e7d8b25b887e536ad17a81ba2dce5476135c73312bd"}, +] + +[package.dependencies] +asgiref = ">=3.7.0,<4" +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-widget-tweaks" +version = "1.5.0" +description = "Tweak the form field rendering in templates, not in python-level form definitions." +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-widget-tweaks-1.5.0.tar.gz", hash = "sha256:1c2180681ebb994e922c754804c7ffebbe1245014777ac47897a81f57cc629c7"}, + {file = "django_widget_tweaks-1.5.0-py3-none-any.whl", hash = "sha256:a41b7b2f05bd44d673d11ebd6c09a96f1d013ee98121cb98c384fe84e33b881e"}, +] + +[[package]] +name = "sqlparse" +version = "0.4.4" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "74287bba169d823cc942c30ea4cd11f7b753f461326b223f4777ec6504e30c22" diff --git a/12 - Desenvolvimento fullstack com Django/desafio/pyproject.toml b/12 - Desenvolvimento fullstack com Django/desafio/pyproject.toml new file mode 100644 index 000000000..f34feac0c --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "desafio" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +django = "*" +django-widget-tweaks = "*" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/12 - Desenvolvimento fullstack com Django/desafio/templates/base.html b/12 - Desenvolvimento fullstack com Django/desafio/templates/base.html new file mode 100644 index 000000000..79f5cc511 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/templates/base.html @@ -0,0 +1,16 @@ + + + + + + {% block title %}Desafio Django{% endblock %} + + + +
+ {% block content %} + {% endblock %} + +
+ + \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/desafio/templates/home.html b/12 - Desenvolvimento fullstack com Django/desafio/templates/home.html new file mode 100644 index 000000000..77a0c2290 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/templates/home.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +{% if user.is_authenticated %} +

Minhas solicitações

+

Olá {{ user.username }}!

+
+ {% csrf_token %} + +
+ {% else %} +

Você não está logado

+ Entrar + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/desafio/templates/registration/login.html b/12 - Desenvolvimento fullstack com Django/desafio/templates/registration/login.html new file mode 100644 index 000000000..646b167cc --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/templates/registration/login.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} + +{% block title %}Login{% endblock %} + +{% load widget_tweaks %} + +{% block content %} +
+
+ {% csrf_token %} + + {% if form.non_field_errors %} + + {% endif %} + + {% for field in form %} +
+ + {% if field.errors %} + {% render_field field class="form-control is-invalid" %} +
+ {{ field.errors|first }} +
+ {% else %} + {% render_field field class="form-control" %} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% endif %} +
+ {% endfor %} + +
+
+{% endblock %} diff --git a/12 - Desenvolvimento fullstack com Django/mysite/.gitignore b/12 - Desenvolvimento fullstack com Django/mysite/.gitignore new file mode 100644 index 000000000..ca9e27cb4 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/.gitignore @@ -0,0 +1,174 @@ +# Created by https://www.toptal.com/developers/gitignore/api/django +# Edit at https://www.toptal.com/developers/gitignore?templates=django + +### Django ### +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media + +# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ +# in your Git repository. Update and uncomment the following line accordingly. +# /staticfiles/ + +### Django.Python Stack ### +# Byte-compiled / optimized / DLL files +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo + +# Django stuff: + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# End of https://www.toptal.com/developers/gitignore/api/django \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/accounts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/apps.py b/12 - Desenvolvimento fullstack com Django/mysite/accounts/apps.py new file mode 100644 index 000000000..3e3c76595 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounts' diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/migrations/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/accounts/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/templates/accounts/login.html b/12 - Desenvolvimento fullstack com Django/mysite/accounts/templates/accounts/login.html new file mode 100644 index 000000000..4008dbde4 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/accounts/templates/accounts/login.html @@ -0,0 +1,10 @@ +

Login

+{% if message %} +

{{message}}

+{% endif %} +
+ {% csrf_token %} + + + +
\ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/urls.py b/12 - Desenvolvimento fullstack com Django/mysite/accounts/urls.py new file mode 100644 index 000000000..2c850dc58 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/accounts/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "accounts" +urlpatterns = [ + path("login/", views.authenticate_user, name="login"), + path("logout/", views.logout_user, name="logout"), +] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/accounts/views.py b/12 - Desenvolvimento fullstack com Django/mysite/accounts/views.py new file mode 100644 index 000000000..4ab3377c6 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/accounts/views.py @@ -0,0 +1,27 @@ +from django.contrib.auth import authenticate, login, logout +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse + + +def authenticate_user(request): + context = {} + + if request.method == "POST": + username = request.POST["username"] + password = request.POST["password"] + user = authenticate(request, username=username, password=password) + + if user is not None: + login(request, user) + return HttpResponseRedirect(reverse("contacts:create")) + else: + context["message"] = "Usuário ou senha inválidos!" + return render(request, "accounts/login.html", context) + + return render(request, "accounts/login.html", context) + + +def logout_user(request): + logout(request) + return HttpResponseRedirect(reverse("accounts:login")) diff --git a/12 - Desenvolvimento fullstack com Django/mysite/build.sh b/12 - Desenvolvimento fullstack com Django/mysite/build.sh new file mode 100755 index 000000000..b172b824f --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Exit on error +set -o errexit + +# Modify this line as needed for your package manager (pip, poetry, etc.) +pip install poetry -U +poetry install --no-root --no-dev + +# Convert static asset files +python manage.py collectstatic --no-input + +# Apply any outstanding database migrations +python manage.py migrate \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/admin.py b/12 - Desenvolvimento fullstack com Django/mysite/config/admin.py new file mode 100644 index 000000000..9d3448ad4 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/config/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from django.contrib.auth.admin import GroupAdmin, UserAdmin +from django.contrib.auth.models import Group, User +from polls.models import Choice, Question + + +class CustomAdminSite(admin.AdminSite): + site_header = "Curso de Python Admin" + + +admin_site = CustomAdminSite() +admin_site.register(Choice) +admin_site.register(Question) +admin_site.register(Group, GroupAdmin) +admin_site.register(User, UserAdmin) diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/asgi.py b/12 - Desenvolvimento fullstack com Django/mysite/config/asgi.py new file mode 100644 index 000000000..856079b2e --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/config/asgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_asgi_application() diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/settings.py b/12 - Desenvolvimento fullstack com Django/mysite/config/settings.py new file mode 100644 index 000000000..2bf89e310 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/config/settings.py @@ -0,0 +1,129 @@ +import os +from pathlib import Path + +import dj_database_url + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = os.environ.get("SECRET_KEY", default="secret") + +DEBUG = "RENDER" not in os.environ + +ALLOWED_HOSTS = [] + +RENDER_EXTERNAL_HOSTNAME = os.environ.get("RENDER_EXTERNAL_HOSTNAME") +if RENDER_EXTERNAL_HOSTNAME: + ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME) + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin.apps.SimpleAdminConfig", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "polls.apps.PollsConfig", + "contacts.apps.ContactsConfig", + "accounts.apps.AccountsConfig", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [Path(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +if not DEBUG: + DATABASES["default"] = dj_database_url.config( + default=os.getenv("DATABASE_URL"), + conn_max_age=600, + ) + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = "pt-br" + +TIME_ZONE = "America/Sao_Paulo" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = "static/" + +if not DEBUG: + # Tell Django to copy static assets into a path called `staticfiles` (this is specific to Render) + STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") + # Enable the WhiteNoise storage backend, which compresses static files to reduce disk use + # and renames the files with unique names for each version to support long-term caching + STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/urls.py b/12 - Desenvolvimento fullstack com Django/mysite/config/urls.py new file mode 100644 index 000000000..06774d894 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/config/urls.py @@ -0,0 +1,9 @@ +from config.admin import admin_site +from django.urls import include, path + +urlpatterns = [ + path("admin/", admin_site.urls), + path("polls/", include("polls.urls")), + path("contacts/", include("contacts.urls")), + path("accounts/", include("accounts.urls")), +] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/config/wsgi.py b/12 - Desenvolvimento fullstack com Django/mysite/config/wsgi.py new file mode 100644 index 000000000..850933564 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/config/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_wsgi_application() diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/apps.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/apps.py new file mode 100644 index 000000000..46700f970 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ContactsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'contacts' diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/forms.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/forms.py new file mode 100644 index 000000000..770caf151 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/forms.py @@ -0,0 +1,13 @@ +from django import forms + +from .models import Contact + + +class NameForm(forms.Form): + your_name = forms.CharField(label="Seu nome", max_length=100) + + +class ContactForm(forms.ModelForm): + class Meta: + model = Contact + fields = "__all__" diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/0001_initial.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/0001_initial.py new file mode 100644 index 000000000..19f81a7eb --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.1 on 2024-02-20 18:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Contact', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('subject', models.CharField(max_length=100)), + ('message', models.CharField(max_length=250)), + ('sender', models.EmailField(max_length=254)), + ('cc_myself', models.BooleanField(blank=True, null=True)), + ], + ), + ] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/models.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/models.py new file mode 100644 index 000000000..2c762c908 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/models.py @@ -0,0 +1,8 @@ +from django.db import models + + +class Contact(models.Model): + subject = models.CharField(max_length=100) + message = models.CharField(max_length=250) + sender = models.EmailField() + cc_myself = models.BooleanField(null=True, blank=True) diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/create.html b/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/create.html new file mode 100644 index 000000000..85d61ac73 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/create.html @@ -0,0 +1,6 @@ +Sair +
+ {%csrf_token%} + {{form}} + +
\ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/name.html b/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/name.html new file mode 100644 index 000000000..d1729ac5a --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/templates/contacts/name.html @@ -0,0 +1,5 @@ +
+ {% csrf_token %} + {{ form }} + +
\ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/urls.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/urls.py new file mode 100644 index 000000000..089c84b17 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +app_name = "contacts" +urlpatterns = [ + path("", views.get_name, name="get_name"), + path("thanks/", views.thanks, name="thanks"), + path("create/", views.create, name="create"), +] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/contacts/views.py b/12 - Desenvolvimento fullstack com Django/mysite/contacts/views.py new file mode 100644 index 000000000..75780af06 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/contacts/views.py @@ -0,0 +1,34 @@ +from django.contrib.auth.decorators import permission_required +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse + +from .forms import ContactForm, NameForm + + +@permission_required("contacts.add_contact") +def create(request): + if request.method == "POST": + form = ContactForm(request.POST) + if form.is_valid(): + name = form.cleaned_data["subject"] + form.save() + return HttpResponseRedirect(reverse("contacts:thanks", args=(name,))) + else: + form = ContactForm() + return render(request, "contacts/create.html", {"form": form}) + + +def get_name(request): + if request.method == "POST": + form = NameForm(request.POST) + if form.is_valid(): + name = form.cleaned_data["your_name"] + return HttpResponseRedirect(reverse("contacts:thanks", args=(name,))) + else: + form = NameForm() + return render(request, "contacts/name.html", {"form": form}) + + +def thanks(request, name): + return HttpResponse(f"Obrigado {name}!") diff --git a/12 - Desenvolvimento fullstack com Django/mysite/manage.py b/12 - Desenvolvimento fullstack com Django/mysite/manage.py new file mode 100755 index 000000000..d28672eae --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.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() diff --git a/12 - Desenvolvimento fullstack com Django/mysite/poetry.lock b/12 - Desenvolvimento fullstack com Django/mysite/poetry.lock new file mode 100644 index 000000000..a3b6f74b8 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/poetry.lock @@ -0,0 +1,639 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "brotli" +version = "1.1.0" +description = "Python bindings for the Brotli compression library" +optional = false +python-versions = "*" +files = [ + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, + {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, + {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, + {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, + {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, + {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, + {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, + {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "dj-database-url" +version = "2.1.0" +description = "Use Database URLs in your Django Application." +optional = false +python-versions = "*" +files = [ + {file = "dj-database-url-2.1.0.tar.gz", hash = "sha256:f2042cefe1086e539c9da39fad5ad7f61173bf79665e69bf7e4de55fa88b135f"}, + {file = "dj_database_url-2.1.0-py3-none-any.whl", hash = "sha256:04bc34b248d4c21aaa13e4ab419ae6575ef5f10f3df735ce7da97722caa356e0"}, +] + +[package.dependencies] +Django = ">=3.2" +typing-extensions = ">=3.10.0.0" + +[[package]] +name = "django" +version = "5.0.1" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.10" +files = [ + {file = "Django-5.0.1-py3-none-any.whl", hash = "sha256:f47a37a90b9bbe2c8ec360235192c7fddfdc832206fcf618bb849b39256affc1"}, + {file = "Django-5.0.1.tar.gz", hash = "sha256:8c8659665bc6e3a44fefe1ab0a291e5a3fb3979f9a8230be29de975e57e8f854"}, +] + +[package.dependencies] +asgiref = ">=3.7.0,<4" +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "gunicorn" +version = "21.2.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, + {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipython" +version = "8.20.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"}, + {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath", "trio"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.0.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, + {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.3.0,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-django" +version = "4.8.0" +description = "A Django plugin for pytest." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, + {file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["Django", "django-configurations (>=2.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sqlparse" +version = "0.4.4" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "traitlets" +version = "5.14.1" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "tzdata" +version = "2023.4" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "whitenoise" +version = "6.6.0" +description = "Radically simplified static file serving for WSGI applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "whitenoise-6.6.0-py3-none-any.whl", hash = "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146"}, + {file = "whitenoise-6.6.0.tar.gz", hash = "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251"}, +] + +[package.dependencies] +Brotli = {version = "*", optional = true, markers = "extra == \"brotli\""} + +[package.extras] +brotli = ["Brotli"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "d8f62c26c9bc73c0c0ae283c3419b6ef2e28142e11f429bda0901bfccf6a4ea3" diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/admin.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/admin.py new file mode 100644 index 000000000..672e7dd46 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from .models import Choice, Question + + +@admin.register(Choice) +class ChoiceAdmin(admin.ModelAdmin): + pass + + +@admin.register(Question) +class QuestionAdmin(admin.ModelAdmin): + pass diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/apps.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/apps.py new file mode 100644 index 000000000..8adde8115 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class PollsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "polls" + verbose_name = "Enquetes" diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0001_initial.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0001_initial.py new file mode 100644 index 000000000..e6a743291 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 5.0.1 on 2024-01-28 20:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Question", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("question_text", models.CharField(max_length=200)), + ("pub_date", models.DateTimeField(verbose_name="date published")), + ], + ), + migrations.CreateModel( + name="Choice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("choice_text", models.CharField(max_length=200)), + ("votes", models.IntegerField(default=0)), + ( + "question", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="polls.question" + ), + ), + ], + ), + ] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0002_question_active.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0002_question_active.py new file mode 100644 index 000000000..58440216c --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0002_question_active.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.1 on 2024-01-28 20:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("polls", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="question", + name="active", + field=models.BooleanField(default=True), + ), + ] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0003_alter_choice_options_alter_question_options_and_more.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0003_alter_choice_options_alter_question_options_and_more.py new file mode 100644 index 000000000..977788274 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/0003_alter_choice_options_alter_question_options_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 5.0.1 on 2024-02-20 18:38 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('polls', '0002_question_active'), + ] + + operations = [ + migrations.AlterModelOptions( + name='choice', + options={'verbose_name': 'Opção', 'verbose_name_plural': 'Opções'}, + ), + migrations.AlterModelOptions( + name='question', + options={'verbose_name': 'Questão', 'verbose_name_plural': 'Questões'}, + ), + migrations.AlterField( + model_name='choice', + name='choice_text', + field=models.CharField(max_length=200, verbose_name='Descrição'), + ), + migrations.AlterField( + model_name='choice', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.question', verbose_name='Questão'), + ), + migrations.AlterField( + model_name='choice', + name='votes', + field=models.IntegerField(default=0, verbose_name='Votos'), + ), + migrations.AlterField( + model_name='question', + name='active', + field=models.BooleanField(default=True, verbose_name='Ativo'), + ), + migrations.AlterField( + model_name='question', + name='pub_date', + field=models.DateTimeField(verbose_name='Data da publicação'), + ), + migrations.AlterField( + model_name='question', + name='question_text', + field=models.CharField(max_length=200, verbose_name='Texto da questão'), + ), + ] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/models.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/models.py new file mode 100644 index 000000000..90adcf770 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/models.py @@ -0,0 +1,33 @@ +import datetime + +from django.db import models +from django.utils import timezone + + +class Question(models.Model): + question_text = models.CharField("Texto da questão", max_length=200) + pub_date = models.DateTimeField("Data da publicação") + active = models.BooleanField("Ativo", default=True) + + class Meta: + verbose_name = "Questão" + verbose_name_plural = "Questões" + + def was_published_recently(self): + return self.pub_date >= timezone.now() - datetime.timedelta(days=1) + + def __str__(self) -> str: + return self.question_text + + +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name="Questão") + choice_text = models.CharField("Descrição", max_length=200) + votes = models.IntegerField("Votos", default=0) + + class Meta: + verbose_name = "Opção" + verbose_name_plural = "Opções" + + def __str__(self) -> str: + return self.choice_text diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/detail.html b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/detail.html new file mode 100644 index 000000000..6df8776f9 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/detail.html @@ -0,0 +1,12 @@ +
+ {% csrf_token %} +
+

{{ question.question_text }}

+ {% if error_message %}

{{ error_message }}

{% endif %} + {% for choice in question.choice_set.all %} + +
+ {% endfor %} +
+ +
\ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/index.html b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/index.html new file mode 100644 index 000000000..44224b1fb --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/index.html @@ -0,0 +1,9 @@ +{% if latest_question_list %} + +{% else %} +

Nenhuma enquete foi criada.

+{% endif %} \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/results.html b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/results.html new file mode 100644 index 000000000..2174f2e69 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/templates/polls/results.html @@ -0,0 +1,9 @@ +

{{ question.question_text }}

+ +
    +{% for choice in question.choice_set.all %} +
  • {{ choice.choice_text }} -- {{ choice.votes }} voto{{ choice.votes|pluralize }}
  • +{% endfor %} +
+ +Vote novamente? \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/urls.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/urls.py new file mode 100644 index 000000000..21dff7e0d --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +app_name = "polls" +urlpatterns = [ + path("", views.index, name="index"), + path("/", views.detail, name="detail"), + path("/results/", views.results, name="results"), + path("/vote/", views.vote, name="vote"), +] diff --git a/12 - Desenvolvimento fullstack com Django/mysite/polls/views.py b/12 - Desenvolvimento fullstack com Django/mysite/polls/views.py new file mode 100644 index 000000000..9a5ec3909 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/polls/views.py @@ -0,0 +1,41 @@ +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.urls import reverse +from polls.models import Choice, Question + + +def index(request): + latest_question_list = Question.objects.order_by("-pub_date")[:5] + context = { + "latest_question_list": latest_question_list, + } + return render(request, "polls/index.html", context) + + +def detail(request, question_id): + question = get_object_or_404(Question, pk=question_id) + return render(request, "polls/detail.html", {"question": question}) + + +def results(request, question_id): + question = get_object_or_404(Question, pk=question_id) + return render(request, "polls/results.html", {"question": question}) + + +def vote(request, question_id): + question = get_object_or_404(Question, pk=question_id) + try: + selected_choice = question.choice_set.get(pk=request.POST["choice"]) + except (KeyError, Choice.DoesNotExist): + return render( + request, + "polls/detail.html", + { + "question": question, + "error_message": "Você não selecionou nenhuma opção.", + }, + ) + else: + selected_choice.votes += 1 + selected_choice.save() + return HttpResponseRedirect(reverse("polls:results", args=(question.id,))) diff --git a/12 - Desenvolvimento fullstack com Django/mysite/pyproject.toml b/12 - Desenvolvimento fullstack com Django/mysite/pyproject.toml new file mode 100644 index 000000000..e4069563e --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "12 - desenvolvimento fullstack com django" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +django = "*" +dj-database-url = "*" +psycopg2-binary = "*" +whitenoise = { version = "*", extras = ["brotli"] } +gunicorn = "*" + + +[tool.poetry.group.dev.dependencies] +ipython = "*" +pytest-django = "*" + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "config.settings" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/12 - Desenvolvimento fullstack com Django/mysite/templates/admin/base.html b/12 - Desenvolvimento fullstack com Django/mysite/templates/admin/base.html new file mode 100644 index 000000000..84fa884ea --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/templates/admin/base.html @@ -0,0 +1,12 @@ +{% extends 'admin/base.html' %} + +{% block extrastyle %}{{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/12 - Desenvolvimento fullstack com Django/mysite/tests/__init__.py b/12 - Desenvolvimento fullstack com Django/mysite/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_forms.py b/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_forms.py new file mode 100644 index 000000000..3b9e7e433 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_forms.py @@ -0,0 +1,40 @@ +from contacts.forms import NameForm + + +def test_name_form_success(): + # Given + data = {"your_name": "Jonh"} + form = NameForm(data=data) + + # When + result = form.is_valid() + + # Then + assert result is True + + +def test_name_form_your_name_max_length(): + # Given + data = {"your_name": "Jonh" * 50} + form = NameForm(data=data) + + # When + result = form.is_valid() + + # Then + assert result is False + assert form.errors == { + "your_name": ["Certifique-se de que o valor tenha no máximo 100 caracteres (ele possui 200)."] + } + + +def test_name_form_fail(): + # Given + data = {} + form = NameForm(data=data) + + # When + result = form.is_valid() + + # Then + assert result is False diff --git a/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_views.py b/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_views.py new file mode 100644 index 000000000..b917f00f9 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/tests/contacts/test_views.py @@ -0,0 +1,51 @@ +from http import HTTPStatus + +import pytest +from django.contrib.auth.models import Permission +from django.urls import reverse + + +def test_contacts_thanks(client): + # Given + name = "Jhon" + + # Then + response = client.get(reverse("contacts:thanks", args=(name,))) + + # When + assert response.status_code == HTTPStatus.OK + assert f"Obrigado {name}!" in response.content.decode() + + +def test_contact_create_with_unauthenticated_user(client): + # Given + url = f'{reverse("accounts:login")}?next={reverse("contacts:create")}' + + # Then + response = client.get(reverse("contacts:create")) + + # When + assert response.status_code == HTTPStatus.FOUND + assert response.url == url + + +@pytest.mark.django_db +def test_contact_create_success(client, django_user_model): + # Given + data = { + "subject": "subject@testmail.com", + "message": "Hello world!", + "sender": "sender@testemail.com", + "cc_myself": True, + } + user = django_user_model.objects.create_user(username="john", email="john@testmail.com", password="123mudar") + permission = Permission.objects.get(codename="add_contact") + user.user_permissions.add(permission) + + # Then + client.force_login(user) + response = client.post(reverse("contacts:create"), data) + + # When + assert response.status_code == HTTPStatus.FOUND + assert response.url == reverse("contacts:thanks", args=(data["subject"],)) diff --git a/12 - Desenvolvimento fullstack com Django/mysite/tests/polls/test_models.py b/12 - Desenvolvimento fullstack com Django/mysite/tests/polls/test_models.py new file mode 100644 index 000000000..b09bba3ce --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/mysite/tests/polls/test_models.py @@ -0,0 +1,17 @@ +import pytest +from django.utils import timezone +from polls.models import Question + + +@pytest.mark.django_db +def test_question_was_published_recently_success(): + # Given + question_text = "Qual é sua linguagem de programação favorita?" + pub_date = timezone.now() + active = True + + # When + question = Question.objects.create(question_text=question_text, pub_date=pub_date, active=active) + + # Then + assert question.was_published_recently() is True From 57fa8181981efd140e209d2c81f19865e5e33fe7 Mon Sep 17 00:00:00 2001 From: Guilherme Carvalho Date: Sat, 27 Apr 2024 15:31:49 -0300 Subject: [PATCH 12/16] feat: add module 13 files --- .../.vscode/launch.json" | 17 + .../desafio/.env.example" | 2 + .../desafio/.gitignore" | 179 +++ .../desafio/README.md" | 22 + .../desafio/alembic.ini" | 116 ++ .../desafio/migrations/README" | 1 + .../desafio/migrations/env.py" | 67 + .../desafio/migrations/script.py.mako" | 26 + .../09f7da264602_add_initial_tables.py" | 48 + .../desafio/poetry.lock" | 1105 +++++++++++++++ .../desafio/pyproject.toml" | 23 + .../desafio/src/config.py" | 11 + .../desafio/src/controllers/account.py" | 27 + .../desafio/src/controllers/auth.py" | 12 + .../desafio/src/controllers/transaction.py" | 15 + .../desafio/src/database.py" | 12 + .../desafio/src/exceptions.py" | 6 + .../desafio/src/main.py" | 77 + .../desafio/src/models/account.py" | 12 + .../desafio/src/models/transaction.py" | 21 + .../desafio/src/schemas/account.py" | 6 + .../desafio/src/schemas/auth.py" | 5 + .../desafio/src/schemas/transaction.py" | 17 + .../desafio/src/security.py" | 90 ++ .../desafio/src/services/account.py" | 18 + .../desafio/src/services/transaction.py" | 47 + .../desafio/src/views/account.py" | 16 + .../desafio/src/views/auth.py" | 5 + .../desafio/src/views/transaction.py" | 9 + .../dio-blog/.env.example" | 2 + .../dio-blog/.gitignore" | 179 +++ .../dio-blog/README.md" | 1 + .../dio-blog/alembic.ini" | 116 ++ .../dio-blog/migrations/README" | 1 + .../dio-blog/migrations/env.py" | 66 + .../dio-blog/migrations/script.py.mako" | 26 + .../bb8893ff2f00_add_initial_tables.py" | 38 + .../dio-blog/poetry.lock" | 1253 +++++++++++++++++ .../dio-blog/pyproject.toml" | 30 + .../dio-blog/render-deploy.sh" | 5 + .../dio-blog/src/config.py" | 11 + .../dio-blog/src/controllers/auth.py" | 12 + .../dio-blog/src/controllers/post.py" | 35 + .../dio-blog/src/database.py" | 12 + .../dio-blog/src/exceptions.py" | 7 + .../dio-blog/src/main.py" | 85 ++ .../dio-blog/src/models/post.py" | 13 + .../dio-blog/src/schemas/auth.py" | 5 + .../dio-blog/src/schemas/post.py" | 15 + .../dio-blog/src/security.py" | 79 ++ .../dio-blog/src/services/post.py" | 51 + .../dio-blog/src/views/auth.py" | 5 + .../dio-blog/src/views/post.py" | 8 + .../dio-blog/tests/__init__.py" | 0 .../dio-blog/tests/conftest.py" | 45 + .../controllers/auth/test_login.py" | 14 + .../controllers/post/test_create_post.py" | 43 + .../controllers/post/test_delete_post.py" | 49 + .../controllers/post/test_read_all.py" | 68 + .../controllers/post/test_read_post.py" | 52 + .../controllers/post/test_update_post.py" | 54 + 61 files changed, 4392 insertions(+) create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/.vscode/launch.json" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/.env.example" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/.gitignore" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/README.md" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/alembic.ini" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/README" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/env.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/script.py.mako" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/versions/09f7da264602_add_initial_tables.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/poetry.lock" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/pyproject.toml" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/config.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/account.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/transaction.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/database.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/exceptions.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/main.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/account.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/transaction.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/account.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/transaction.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/security.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/account.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/transaction.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/account.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/transaction.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.env.example" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.gitignore" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/README.md" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/alembic.ini" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/README" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/env.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/script.py.mako" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/versions/bb8893ff2f00_add_initial_tables.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/poetry.lock" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/pyproject.toml" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/render-deploy.sh" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/config.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/database.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/exceptions.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/main.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/models/post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/security.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/services/post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/auth.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/__init__.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/conftest.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/auth/test_login.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_create_post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_delete_post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_all.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_post.py" create mode 100644 "13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_update_post.py" diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/.vscode/launch.json" "b/13 - APIs Ass\303\255ncronas com FastAPI/.vscode/launch.json" new file mode 100644 index 000000000..fe58b99c8 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/.vscode/launch.json" @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Depurador do Python: FastAPI", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "cwd": "${workspaceFolder}/dio-blog", + "args": ["main:app", "--reload"], + "jinja": true + } + ] +} diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.env.example" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.env.example" new file mode 100644 index 000000000..a8f185af1 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.env.example" @@ -0,0 +1,2 @@ +ENVIRONMENT="local" +DATABASE_URL="sqlite:///./bank.db" \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.gitignore" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.gitignore" new file mode 100644 index 000000000..8a59e5377 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/.gitignore" @@ -0,0 +1,179 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +*.db + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +.vscode/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/README.md" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/README.md" new file mode 100644 index 000000000..574f11a82 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/README.md" @@ -0,0 +1,22 @@ +# Desafio: API Bancária Assíncrona com FastAPI + +Neste desafio, você irá projetar e implementar uma API RESTful assíncrona usando FastAPI para gerenciar operações bancárias de depósitos e saques, vinculadas a contas correntes. Este desafio irá lhe proporcionar a experiência de construir uma aplicação backend moderna e eficiente que utiliza autenticação JWT e práticas recomendadas de design de APIs. + +## Objetivos e Funcionalidades + +O objetivo deste desafio é desenvolver uma API com as seguintes funcionalidades: + +- **Cadastro de Transações:** Permita o cadastro de transações bancárias, como depósitos e saques. +- **Exibição de Extrato:** Implemente um endpoint para exibir o extrato de uma conta, mostrando todas as transações realizadas. +- **Autenticação com JWT:** Utilize JWT (JSON Web Tokens) para garantir que apenas usuários autenticados possam acessar os endpoints que exigem autenticação. + +## Requisitos Técnicos + +Para a realização deste desafio, você deve atender aos seguintes requisitos técnicos: + +- **FastAPI:** Utilize FastAPI como framework para criar sua API. Aproveite os recursos assíncronos do framework para lidar com operações de I/O de forma eficiente. +- **Modelagem de Dados:** Crie modelos de dados adequados para representar contas correntes e transações. Garanta que as transações estão relacionadas a uma conta corrente, e que contas possam ter múltiplas transações. +- **Validação das operações:** Não permita depósitos e saques com valores negativos, valide se o usuário possui saldo para realizar o saque. +- **Segurança:** Implemente autenticação usando JWT para proteger os endpoints que necessitam de acesso autenticado. +- **Documentação com OpenAPI:** Certifique-se de que sua API esteja bem documentada, incluindo descrições adequad +as para cada endpoint, parâmetros e modelos de dados. \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/alembic.ini" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/alembic.ini" new file mode 100644 index 000000000..cd413a260 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/alembic.ini" @@ -0,0 +1,116 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/README" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/README" new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/README" @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/env.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/env.py" new file mode 100644 index 000000000..fcc09a545 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/env.py" @@ -0,0 +1,67 @@ +from logging.config import fileConfig + +from alembic import context + +from src.config import settings + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + + +from src.database import engine, metadata # noqa +from src.models.transaction import transactions # noqa +from src.models.account import accounts # noqa + +target_metadata = metadata + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = settings.database_url + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/script.py.mako" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/script.py.mako" new file mode 100644 index 000000000..fbc4b07dc --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/script.py.mako" @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/versions/09f7da264602_add_initial_tables.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/versions/09f7da264602_add_initial_tables.py" new file mode 100644 index 000000000..06d6e9e98 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/migrations/versions/09f7da264602_add_initial_tables.py" @@ -0,0 +1,48 @@ +"""Add initial tables + +Revision ID: 09f7da264602 +Revises: +Create Date: 2024-04-16 19:45:59.524806 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '09f7da264602' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('accounts', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('balance', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_accounts_user_id'), 'accounts', ['user_id'], unique=False) + op.create_table('transactions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('account_id', sa.Integer(), nullable=False), + sa.Column('type', sa.Enum('DEPOSIT', 'WITHDRAWAL', name='transaction_types'), nullable=False), + sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('timestamp', sa.TIMESTAMP(timezone=True), nullable=True), + sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('transactions') + op.drop_index(op.f('ix_accounts_user_id'), table_name='accounts') + op.drop_table('accounts') + # ### end Alembic commands ### diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/poetry.lock" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/poetry.lock" new file mode 100644 index 000000000..9f1cc9194 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/poetry.lock" @@ -0,0 +1,1105 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "aiosqlite" +version = "0.20.0" +description = "asyncio bridge to the standard sqlite3 module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, + {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, +] + +[package.dependencies] +typing_extensions = ">=4.0" + +[package.extras] +dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] + +[[package]] +name = "alembic" +version = "1.13.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "databases" +version = "0.9.0" +description = "Async database support for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "databases-0.9.0-py3-none-any.whl", hash = "sha256:9ee657c9863b34f8d3a06c06eafbe1bda68af2a434b56996312edf1f1c0b6297"}, + {file = "databases-0.9.0.tar.gz", hash = "sha256:d2f259677609bf187737644c95fa41701072e995dfeb8d2882f335795c5b61b0"}, +] + +[package.dependencies] +aiosqlite = {version = "*", optional = true, markers = "extra == \"aiosqlite\""} +asyncpg = {version = "*", optional = true, markers = "extra == \"asyncpg\""} +sqlalchemy = ">=2.0.7" + +[package.extras] +aiomysql = ["aiomysql"] +aiopg = ["aiopg"] +aiosqlite = ["aiosqlite"] +asyncmy = ["asyncmy"] +asyncpg = ["asyncpg"] +mysql = ["aiomysql"] +postgresql = ["asyncpg"] +sqlite = ["aiosqlite"] + +[[package]] +name = "fastapi" +version = "0.110.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"}, + {file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httptools" +version = "0.6.1" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, + {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, + {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, + {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, + {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, + {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, + {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "mako" +version = "1.3.3" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.3-py3-none-any.whl", hash = "sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40"}, + {file = "Mako-1.3.3.tar.gz", hash = "sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "pydantic" +version = "2.7.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"}, + {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.1" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"}, + {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"}, + {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"}, + {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"}, + {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"}, + {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"}, + {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"}, + {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"}, + {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"}, + {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"}, + {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"}, + {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"}, + {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"}, + {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"}, + {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"}, + {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"}, + {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"}, + {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"}, + {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"}, + {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"}, + {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"}, + {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"}, + {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"}, + {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"}, + {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"}, + {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"}, + {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"}, + {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"}, + {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"}, + {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"}, + {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"}, + {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"}, + {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.2.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, +] + +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" + +[package.extras] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.29" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, + {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, + {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[[package]] +name = "uvicorn" +version = "0.29.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, + {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "watchfiles" +version = "0.21.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, + {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, + {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, + {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, + {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, + {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, + {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, + {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, + {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, + {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, + {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, + {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, + {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, + {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, + {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, + {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, + {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, + {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, + {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, + {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, + {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, + {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, + {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, + {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, + {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, + {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, + {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, + {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, + {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, + {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, + {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, + {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, + {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "97d62104aaba9529d9becebe0ddd64f7c90be3c9b8b78f9c87e689bd4d025789" diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/pyproject.toml" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/pyproject.toml" new file mode 100644 index 000000000..855266a89 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/pyproject.toml" @@ -0,0 +1,23 @@ +[tool.poetry] +name = "dio-blog" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "*" +uvicorn = { extras = ["standard"], version = "*" } +databases = { version = "*", extras = ["aiosqlite", "asyncpg"] } +pyjwt = "*" +psycopg2-binary = "*" +pydantic-settings = "*" +alembic = "*" + +[tool.ruff] +line-length = 120 + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/config.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/config.py" new file mode 100644 index 000000000..a06ec7def --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/config.py" @@ -0,0 +1,11 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_file_encoding="utf-8") + + database_url: str + environment: str = "production" + + +settings = Settings() diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/account.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/account.py" new file mode 100644 index 000000000..7aff5384c --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/account.py" @@ -0,0 +1,27 @@ +from fastapi import APIRouter, Depends, status + +from src.schemas.account import AccountIn +from src.security import login_required +from src.services.account import AccountService +from src.services.transaction import TransactionService +from src.views.account import AccountOut, TransactionOut + +router = APIRouter(prefix="/accounts", dependencies=[Depends(login_required)]) + +account_service = AccountService() +tx_service = TransactionService() + + +@router.get("/", response_model=list[AccountOut]) +async def read_accounts(limit: int, skip: int = 0): + return await account_service.read_all(limit=limit, skip=skip) + + +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=AccountOut) +async def create_account(account: AccountIn): + return await account_service.create(account) + + +@router.get("/{id}/transactions", response_model=list[TransactionOut]) +async def read_account_transactions(id: int, limit: int, skip: int = 0): + return await tx_service.read_all(account_id=id, limit=limit, skip=skip) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/auth.py" new file mode 100644 index 000000000..25868038d --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/auth.py" @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from src.schemas.auth import LoginIn +from src.security import sign_jwt +from src.views.auth import LoginOut + +router = APIRouter(prefix="/auth") + + +@router.post("/login", response_model=LoginOut) +async def login(data: LoginIn): + return sign_jwt(user_id=data.user_id) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/transaction.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/transaction.py" new file mode 100644 index 000000000..54df360ee --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/controllers/transaction.py" @@ -0,0 +1,15 @@ +from fastapi import APIRouter, Depends, status + +from src.schemas.transaction import TransactionIn +from src.security import login_required +from src.services.transaction import TransactionService +from src.views.transaction import TransactionOut + +router = APIRouter(prefix="/transactions", dependencies=[Depends(login_required)]) + +service = TransactionService() + + +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=TransactionOut) +async def create_transaction(transaction: TransactionIn): + return await service.create(transaction) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/database.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/database.py" new file mode 100644 index 000000000..a98581960 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/database.py" @@ -0,0 +1,12 @@ +import databases +import sqlalchemy as sa + +from src.config import settings + +database = databases.Database(settings.database_url) +metadata = sa.MetaData() + +if settings.environment == "production": + engine = sa.create_engine(settings.database_url) +else: + engine = sa.create_engine(settings.database_url, connect_args={"check_same_thread": False}) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/exceptions.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/exceptions.py" new file mode 100644 index 000000000..8445a364e --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/exceptions.py" @@ -0,0 +1,6 @@ +class AccountNotFoundError(Exception): + pass + + +class BusinessError(Exception): + pass diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/main.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/main.py" new file mode 100644 index 000000000..7673cbbb7 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/main.py" @@ -0,0 +1,77 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI, Request, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from src.controllers import account, auth, transaction +from src.database import database +from src.exceptions import AccountNotFoundError, BusinessError + + +@asynccontextmanager +async def lifespan(app: FastAPI): + await database.connect() + yield + await database.disconnect() + + +tags_metadata = [ + { + "name": "auth", + "description": "Operations for authentication.", + }, + { + "name": "account", + "description": "Operations to maintain accounts.", + }, + { + "name": "transaction", + "description": "Operations to maintain transactions.", + }, +] + + +app = FastAPI( + title="Transactions API", + version="1.0.0", + summary="Microservice to maintain withdrawal and deposit operations from current accounts.", + description=""" +Transactions API is the microservice for recording current account transactions. 💸💰 + +## Account + +* **Create accounts**. +* **List accounts**. +* **List account transactions by ID**. + +## Transaction + +* **Create transactions**. +""", + openapi_tags=tags_metadata, + redoc_url=None, + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(auth.router, tags=["auth"]) +app.include_router(account.router, tags=["account"]) +app.include_router(transaction.router, tags=["transaction"]) + + +@app.exception_handler(AccountNotFoundError) +async def account_not_found_error_handler(request: Request, exc: AccountNotFoundError): + return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={"detail": "Account not found."}) + + +@app.exception_handler(BusinessError) +async def business_error_handler(request: Request, exc: BusinessError): + return JSONResponse(status_code=status.HTTP_409_CONFLICT, content={"detail": str(exc)}) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/account.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/account.py" new file mode 100644 index 000000000..f2d841741 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/account.py" @@ -0,0 +1,12 @@ +import sqlalchemy as sa + +from src.database import metadata + +accounts = sa.Table( + "accounts", + metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("user_id", sa.Integer, nullable=False, index=True), + sa.Column("balance", sa.Numeric(10, 2), nullable=False, default=0), + sa.Column("created_at", sa.TIMESTAMP(timezone=True), default=sa.func.now()), +) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/transaction.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/transaction.py" new file mode 100644 index 000000000..b6177fb0e --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/models/transaction.py" @@ -0,0 +1,21 @@ +from enum import Enum + +import sqlalchemy as sa + +from src.database import metadata + + +class TransactionType(str, Enum): + DEPOSIT = "deposit" + WITHDRAWAL = "withdrawal" + + +transactions = sa.Table( + "transactions", + metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("account_id", sa.Integer, sa.ForeignKey("accounts.id"), nullable=False), + sa.Column("type", sa.Enum(TransactionType, name="transaction_types"), nullable=False), + sa.Column("amount", sa.Numeric(10, 2), nullable=False), + sa.Column("timestamp", sa.TIMESTAMP(timezone=True), default=sa.func.now()), +) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/account.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/account.py" new file mode 100644 index 000000000..4c1a2f355 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/account.py" @@ -0,0 +1,6 @@ +from pydantic import BaseModel, PositiveFloat + + +class AccountIn(BaseModel): + user_id: int + balance: PositiveFloat diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/auth.py" new file mode 100644 index 000000000..9b4c79e39 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/auth.py" @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class LoginIn(BaseModel): + user_id: int diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/transaction.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/transaction.py" new file mode 100644 index 000000000..c41064b43 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/schemas/transaction.py" @@ -0,0 +1,17 @@ +from enum import Enum + +from pydantic import BaseModel, PositiveFloat + + +class TransactionType(Enum): + DEPOSIT = "deposit" + WITHDRAWAL = "withdrawal" + + +class TransactionIn(BaseModel): + account_id: int + type: TransactionType + amount: PositiveFloat + + class Config: + use_enum_values = True diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/security.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/security.py" new file mode 100644 index 000000000..b0aa062ba --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/security.py" @@ -0,0 +1,90 @@ +import time +from typing import Annotated +from uuid import uuid4 + +import jwt +from fastapi import Depends, HTTPException, Request, status +from fastapi.security import HTTPBearer +from pydantic import BaseModel + +SECRET = "my-secret" +ALGORITHM = "HS256" + + +class AccessToken(BaseModel): + iss: str + sub: int + aud: str + exp: float + iat: float + nbf: float + jti: str + + +class JWTToken(BaseModel): + access_token: AccessToken + + +def sign_jwt(user_id: int) -> JWTToken: + now = time.time() + payload = { + "iss": "desafio-bank.com.br", + "sub": user_id, + "aud": "desafio-bank", + "exp": now + (60 * 30), # 30 minutes + "iat": now, + "nbf": now, + "jti": uuid4().hex, + } + token = jwt.encode(payload, SECRET, algorithm=ALGORITHM) + return {"access_token": token} + + +async def decode_jwt(token: str) -> JWTToken | None: + try: + decoded_token = jwt.decode(token, SECRET, audience="desafio-bank", algorithms=[ALGORITHM]) + _token = JWTToken.model_validate({"access_token": decoded_token}) + return _token if _token.access_token.exp >= time.time() else None + except Exception: + return None + + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request) -> JWTToken: + authorization = request.headers.get("Authorization", "") + scheme, _, credentials = authorization.partition(" ") + + if credentials: + if not scheme == "Bearer": + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication scheme.", + ) + + payload = await decode_jwt(credentials) + if not payload: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired token.", + ) + return payload + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authorization code.", + ) + + +async def get_current_user( + token: Annotated[JWTToken, Depends(JWTBearer())], +) -> dict[str, int]: + return {"user_id": token.access_token.sub} + + +def login_required(current_user: Annotated[dict[str, int], Depends(get_current_user)]): + if not current_user: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") + return current_user diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/account.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/account.py" new file mode 100644 index 000000000..84100f2fa --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/account.py" @@ -0,0 +1,18 @@ +from databases.interfaces import Record + +from src.database import database +from src.models.account import accounts +from src.schemas.account import AccountIn + + +class AccountService: + async def read_all(self, limit: int, skip: int = 0) -> list[Record]: + query = accounts.select().limit(limit).offset(skip) + return await database.fetch_all(query) + + async def create(self, account: AccountIn) -> Record: + command = accounts.insert().values(user_id=account.user_id, balance=account.balance) + account_id = await database.execute(command) + + query = accounts.select().where(accounts.c.id == account_id) + return await database.fetch_one(query) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/transaction.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/transaction.py" new file mode 100644 index 000000000..d5ccde4c7 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/services/transaction.py" @@ -0,0 +1,47 @@ +from databases.interfaces import Record + +from src.database import database +from src.exceptions import AccountNotFoundError, BusinessError +from src.models.account import accounts +from src.models.transaction import TransactionType, transactions +from src.schemas.transaction import TransactionIn + + +class TransactionService: + async def read_all(self, account_id: int, limit: int, skip: int = 0) -> list[Record]: + query = transactions.select().where(transactions.c.account_id == account_id).limit(limit).offset(skip) + return await database.fetch_all(query) + + @database.transaction() + async def create(self, transaction: TransactionIn) -> Record: + query = accounts.select().where(accounts.c.id == transaction.account_id) + account = await database.fetch_one(query) + if not account: + raise AccountNotFoundError + + if transaction.type == TransactionType.WITHDRAWAL: + balance = float(account.balance) - transaction.amount + if balance < 0: + raise BusinessError("Operation not carried out due to lack of balance") + else: + balance = float(account.balance) + transaction.amount + + # Create transaction entry + transaction_id = await self.__register_transaction(transaction) + # Update account balance + await self.__update_account_balance(transaction.account_id, balance) + + query = transactions.select().where(transactions.c.id == transaction_id) + return await database.fetch_one(query) + + async def __update_account_balance(self, account_id: int, balance: float) -> None: + command = accounts.update().where(accounts.c.id == account_id).values(balance=balance) + await database.execute(command) + + async def __register_transaction(self, transaction: TransactionIn) -> int: + command = transactions.insert().values( + account_id=transaction.account_id, + type=transaction.type, + amount=transaction.amount, + ) + return await database.execute(command) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/account.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/account.py" new file mode 100644 index 000000000..f72acf556 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/account.py" @@ -0,0 +1,16 @@ +from pydantic import AwareDatetime, BaseModel, NaiveDatetime, PositiveFloat + + +class AccountOut(BaseModel): + id: int + user_id: int + balance: float + created_at: AwareDatetime | NaiveDatetime + + +class TransactionOut(BaseModel): + id: int + account_id: int + type: str + amount: PositiveFloat + timestamp: AwareDatetime | NaiveDatetime diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/auth.py" new file mode 100644 index 000000000..c4297af75 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/auth.py" @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class LoginOut(BaseModel): + access_token: str diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/transaction.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/transaction.py" new file mode 100644 index 000000000..0f3e63e09 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/desafio/src/views/transaction.py" @@ -0,0 +1,9 @@ +from pydantic import AwareDatetime, BaseModel, NaiveDatetime, PositiveFloat + + +class TransactionOut(BaseModel): + id: int + account_id: int + type: str + amount: PositiveFloat + timestamp: AwareDatetime | NaiveDatetime diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.env.example" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.env.example" new file mode 100644 index 000000000..62f61b7d4 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.env.example" @@ -0,0 +1,2 @@ +ENVIRONMENT="local" +DATABASE_URL="sqlite:///./blog.db" \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.gitignore" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.gitignore" new file mode 100644 index 000000000..8a59e5377 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/.gitignore" @@ -0,0 +1,179 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +*.db + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +.vscode/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/README.md" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/README.md" new file mode 100644 index 000000000..737bb4628 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/README.md" @@ -0,0 +1 @@ +# dio_blog_fastapi diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/alembic.ini" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/alembic.ini" new file mode 100644 index 000000000..cd413a260 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/alembic.ini" @@ -0,0 +1,116 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/README" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/README" new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/README" @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/env.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/env.py" new file mode 100644 index 000000000..63f095949 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/env.py" @@ -0,0 +1,66 @@ +from logging.config import fileConfig + +from alembic import context + +from src.config import settings + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + + +from src.database import engine, metadata # noqa +from src.models.post import posts # noqa + +target_metadata = metadata + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = settings.database_url + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/script.py.mako" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/script.py.mako" new file mode 100644 index 000000000..fbc4b07dc --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/script.py.mako" @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/versions/bb8893ff2f00_add_initial_tables.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/versions/bb8893ff2f00_add_initial_tables.py" new file mode 100644 index 000000000..b74306e9a --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/migrations/versions/bb8893ff2f00_add_initial_tables.py" @@ -0,0 +1,38 @@ +"""Add initial tables + +Revision ID: bb8893ff2f00 +Revises: +Create Date: 2024-04-16 03:17:15.443238 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'bb8893ff2f00' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('posts', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=150), nullable=False), + sa.Column('content', sa.String(), nullable=False), + sa.Column('published_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('published', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('posts') + # ### end Alembic commands ### diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/poetry.lock" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/poetry.lock" new file mode 100644 index 000000000..87e5c8af2 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/poetry.lock" @@ -0,0 +1,1253 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "aiosqlite" +version = "0.20.0" +description = "asyncio bridge to the standard sqlite3 module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, + {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, +] + +[package.dependencies] +typing_extensions = ">=4.0" + +[package.extras] +dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] + +[[package]] +name = "alembic" +version = "1.13.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "databases" +version = "0.9.0" +description = "Async database support for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "databases-0.9.0-py3-none-any.whl", hash = "sha256:9ee657c9863b34f8d3a06c06eafbe1bda68af2a434b56996312edf1f1c0b6297"}, + {file = "databases-0.9.0.tar.gz", hash = "sha256:d2f259677609bf187737644c95fa41701072e995dfeb8d2882f335795c5b61b0"}, +] + +[package.dependencies] +aiosqlite = {version = "*", optional = true, markers = "extra == \"aiosqlite\""} +asyncpg = {version = "*", optional = true, markers = "extra == \"asyncpg\""} +sqlalchemy = ">=2.0.7" + +[package.extras] +aiomysql = ["aiomysql"] +aiopg = ["aiopg"] +aiosqlite = ["aiosqlite"] +asyncmy = ["asyncmy"] +asyncpg = ["asyncpg"] +mysql = ["aiomysql"] +postgresql = ["asyncpg"] +sqlite = ["aiosqlite"] + +[[package]] +name = "fastapi" +version = "0.110.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"}, + {file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httptools" +version = "0.6.1" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, + {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, + {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, + {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, + {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, + {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, + {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "mako" +version = "1.3.3" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.3-py3-none-any.whl", hash = "sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40"}, + {file = "Mako-1.3.3.tar.gz", hash = "sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "pydantic" +version = "2.6.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.2.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, +] + +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" + +[package.extras] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.6" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, + {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.29" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, + {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, + {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[[package]] +name = "uvicorn" +version = "0.29.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, + {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "watchfiles" +version = "0.21.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"}, + {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"}, + {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"}, + {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"}, + {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"}, + {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"}, + {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"}, + {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"}, + {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"}, + {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"}, + {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"}, + {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"}, + {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"}, + {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"}, + {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"}, + {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"}, + {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"}, + {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"}, + {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"}, + {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"}, + {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"}, + {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"}, + {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"}, + {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"}, + {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"}, + {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"}, + {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"}, + {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"}, + {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"}, + {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"}, + {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"}, + {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"}, + {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"}, + {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"}, + {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"}, + {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"}, + {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"}, + {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"}, + {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"}, + {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"}, + {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "db2d36e84aec2013eb716c906ec1aa9bc8578f26bf9812b25d5c5230f3f1de5d" diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/pyproject.toml" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/pyproject.toml" new file mode 100644 index 000000000..453cc8011 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/pyproject.toml" @@ -0,0 +1,30 @@ +[tool.poetry] +name = "dio-blog" +version = "0.1.0" +description = "" +authors = ["Guilherme Carvalho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "*" +uvicorn = { extras = ["standard"], version = "^0.29.0" } +databases = {version = "*", extras = ["aiosqlite", "asyncpg"]} +pyjwt = "*" +psycopg2-binary = "*" +pydantic-settings = "*" +alembic = "*" + + +[tool.poetry.group.dev.dependencies] +pytest-asyncio = "*" +pytest = "*" +httpx = "*" +pytest-mock = "*" + +[tool.pytest.ini_options] +asyncio_mode = "auto" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/render-deploy.sh" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/render-deploy.sh" new file mode 100644 index 000000000..fbe7e34da --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/render-deploy.sh" @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e + +alembic upgrade head +uvicorn src.main:app --host 0.0.0.0 --port $PORT \ No newline at end of file diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/config.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/config.py" new file mode 100644 index 000000000..a06ec7def --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/config.py" @@ -0,0 +1,11 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_file_encoding="utf-8") + + database_url: str + environment: str = "production" + + +settings = Settings() diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/auth.py" new file mode 100644 index 000000000..25868038d --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/auth.py" @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from src.schemas.auth import LoginIn +from src.security import sign_jwt +from src.views.auth import LoginOut + +router = APIRouter(prefix="/auth") + + +@router.post("/login", response_model=LoginOut) +async def login(data: LoginIn): + return sign_jwt(user_id=data.user_id) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/post.py" new file mode 100644 index 000000000..58557df3f --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/controllers/post.py" @@ -0,0 +1,35 @@ +from fastapi import APIRouter, Depends, status + +from src.schemas.post import PostIn, PostUpdateIn +from src.security import login_required +from src.services.post import PostService +from src.views.post import PostOut + +router = APIRouter(prefix="/posts", dependencies=[Depends(login_required)]) + +service = PostService() + + +@router.get("/", response_model=list[PostOut]) +async def read_posts(published: bool, limit: int, skip: int = 0): + return await service.read_all(published=published, limit=limit, skip=skip) + + +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=PostOut) +async def create_post(post: PostIn): + return {**post.model_dump(), "id": await service.create(post)} + + +@router.get("/{id}", response_model=PostOut) +async def read_post(id: int): + return await service.read(id) + + +@router.patch("/{id}", response_model=PostOut) +async def update_post(id: int, post: PostUpdateIn): + return await service.update(id=id, post=post) + + +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +async def delete_post(id: int): + await service.delete(id) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/database.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/database.py" new file mode 100644 index 000000000..a98581960 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/database.py" @@ -0,0 +1,12 @@ +import databases +import sqlalchemy as sa + +from src.config import settings + +database = databases.Database(settings.database_url) +metadata = sa.MetaData() + +if settings.environment == "production": + engine = sa.create_engine(settings.database_url) +else: + engine = sa.create_engine(settings.database_url, connect_args={"check_same_thread": False}) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/exceptions.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/exceptions.py" new file mode 100644 index 000000000..fa2f537e8 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/exceptions.py" @@ -0,0 +1,7 @@ +from http import HTTPStatus + + +class NotFoundPostError(Exception): + def __init__(self, message: str = "Post not found", status_code: int = HTTPStatus.NOT_FOUND) -> None: + self.message = message + self.status_code = status_code diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/main.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/main.py" new file mode 100644 index 000000000..2fbcfaeef --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/main.py" @@ -0,0 +1,85 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from src.controllers import auth, post +from src.database import database +from src.exceptions import NotFoundPostError + + +@asynccontextmanager +async def lifespan(app: FastAPI): + await database.connect() + yield + await database.disconnect() + + +tags_metadata = [ + { + "name": "auth", + "description": "Operações para autenticação", + }, + { + "name": "post", + "description": "Operações para manter posts.", + "externalDocs": { + "description": "Documentação externa para Posts.api", + "url": "https://post-api.com/", + }, + }, +] + +servers = [ + {"url": "http://localhost:8000", "description": "Ambiente de desenvolvimento"}, + { + "url": "https://dio-blog-fastapi.onrender.com", + "description": "Ambiente de produção", + }, +] + + +app = FastAPI( + title="DIO blog API", + version="1.2.0", + summary="API para blog pessoal.", + description=""" +DIO blog API ajuda você a criar seu blog pessoal. 🚀 + +## Posts + +Você será capaz de fazer: + +* **Criar posts**. +* **Recuperar posts**. +* **Recuperar posts por ID**. +* **Atualizar posts**. +* **Excluir posts**. +* **Limitar quantidade de posts diários** (_not implemented_). + """, + openapi_tags=tags_metadata, + servers=servers, + redoc_url=None, + # openapi_url=None, # disable docs + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(auth.router, tags=["auth"]) +app.include_router(post.router, tags=["post"]) + + +@app.exception_handler(NotFoundPostError) +async def not_found_post_exception_handler(request: Request, exc: NotFoundPostError): + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.message}, + ) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/models/post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/models/post.py" new file mode 100644 index 000000000..c9f3444d5 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/models/post.py" @@ -0,0 +1,13 @@ +import sqlalchemy as sa + +from src.database import metadata + +posts = sa.Table( + "posts", + metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("title", sa.String(150), nullable=False, unique=True), + sa.Column("content", sa.String, nullable=False), + sa.Column("published_at", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("published", sa.Boolean, default=False), +) diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/auth.py" new file mode 100644 index 000000000..9b4c79e39 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/auth.py" @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class LoginIn(BaseModel): + user_id: int diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/post.py" new file mode 100644 index 000000000..3773399e0 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/schemas/post.py" @@ -0,0 +1,15 @@ +from pydantic import AwareDatetime, BaseModel + + +class PostIn(BaseModel): + title: str + content: str + published_at: AwareDatetime | None = None + published: bool = False + + +class PostUpdateIn(BaseModel): + title: str | None = None + content: str | None = None + published_at: AwareDatetime | None = None + published: bool | None = None diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/security.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/security.py" new file mode 100644 index 000000000..fa8454faf --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/security.py" @@ -0,0 +1,79 @@ +import time +from typing import Annotated +from uuid import uuid4 + +import jwt +from fastapi import Depends, HTTPException, Request, status +from fastapi.security import HTTPBearer +from pydantic import BaseModel + +SECRET = "my-secret" +ALGORITHM = "HS256" + + +class AccessToken(BaseModel): + iss: str + sub: int + aud: str + exp: float + iat: float + nbf: float + jti: str + + +class JWTToken(BaseModel): + access_token: AccessToken + + +def sign_jwt(user_id: int) -> JWTToken: + now = time.time() + payload = { + "iss": "curso-fastapi.com.br", + "sub": user_id, + "aud": "curso-fastapi", + "exp": now + (60 * 30), # 30 minutes + "iat": now, + "nbf": now, + "jti": uuid4().hex, + } + token = jwt.encode(payload, SECRET, algorithm=ALGORITHM) + return {"access_token": token} + + +async def decode_jwt(token: str) -> JWTToken | None: + try: + decoded_token = jwt.decode(token, SECRET, audience="curso-fastapi", algorithms=[ALGORITHM]) + _token = JWTToken.model_validate({"access_token": decoded_token}) + return _token if _token.access_token.exp >= time.time() else None + except Exception: + return None + + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request) -> JWTToken: + authorization = request.headers.get("Authorization", "") + scheme, _, credentials = authorization.partition(" ") + + if credentials: + if not scheme == "Bearer": + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication scheme.") + + payload = await decode_jwt(credentials) + if not payload: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token.") + return payload + else: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authorization code.") + + +async def get_current_user(token: Annotated[JWTToken, Depends(JWTBearer())]) -> dict[str, int]: + return {"user_id": token.access_token.sub} + + +def login_required(current_user: Annotated[dict[str, int], Depends(get_current_user)]): + if not current_user: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") + return current_user diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/services/post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/services/post.py" new file mode 100644 index 000000000..9a28e40b4 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/services/post.py" @@ -0,0 +1,51 @@ +from databases.interfaces import Record + +from src.database import database +from src.exceptions import NotFoundPostError +from src.models.post import posts +from src.schemas.post import PostIn, PostUpdateIn + + +class PostService: + async def read_all(self, published: bool, limit: int, skip: int = 0) -> list[Record]: + query = posts.select().where(posts.c.published == published).limit(limit).offset(skip) + return await database.fetch_all(query) + + async def create(self, post: PostIn) -> int: + command = posts.insert().values( + title=post.title, + content=post.content, + published_at=post.published_at, + published=post.published, + ) + return await database.execute(command) + + async def read(self, id: int) -> Record: + return await self.__get_by_id(id) + + async def update(self, id: int, post: PostUpdateIn) -> Record: + total = await self.count(id) + if not total: + raise NotFoundPostError + + data = post.model_dump(exclude_unset=True) + command = posts.update().where(posts.c.id == id).values(**data) + await database.execute(command) + + return await self.__get_by_id(id) + + async def delete(self, id: int) -> None: + command = posts.delete().where(posts.c.id == id) + await database.execute(command) + + async def count(self, id: int) -> int: + query = "select count(id) as total from posts where id = :id" + result = await database.fetch_one(query, {"id": id}) + return result.total + + async def __get_by_id(self, id: int) -> Record: + query = posts.select().where(posts.c.id == id) + post = await database.fetch_one(query) + if not post: + raise NotFoundPostError + return post diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/auth.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/auth.py" new file mode 100644 index 000000000..c4297af75 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/auth.py" @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class LoginOut(BaseModel): + access_token: str diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/post.py" new file mode 100644 index 000000000..57cf47881 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/src/views/post.py" @@ -0,0 +1,8 @@ +from pydantic import AwareDatetime, BaseModel, NaiveDatetime + + +class PostOut(BaseModel): + id: int + title: str + content: str + published_at: AwareDatetime | NaiveDatetime | None diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/__init__.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/__init__.py" new file mode 100644 index 000000000..e69de29bb diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/conftest.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/conftest.py" new file mode 100644 index 000000000..fe1f9f49b --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/conftest.py" @@ -0,0 +1,45 @@ +import asyncio + +import pytest_asyncio +from httpx import ASGITransport, AsyncClient + +from src.config import settings + +settings.database_url = "sqlite:///tests.db" + + +@pytest_asyncio.fixture +async def db(request): + from src.database import database, engine, metadata # noqa + from src.models.post import posts # noqa + + await database.connect() + metadata.create_all(engine) + + def teardown(): + async def _teardown(): + await database.disconnect() + metadata.drop_all(engine) + + asyncio.run(_teardown()) + + request.addfinalizer(teardown) + + +@pytest_asyncio.fixture +async def client(db): + from src.main import app + + transport = ASGITransport(app=app) + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + async with AsyncClient(base_url="http://test", transport=transport, headers=headers) as client: + yield client + + +@pytest_asyncio.fixture +async def access_token(client: AsyncClient): + response = await client.post("/auth/login", json={"user_id": 1}) + return response.json()["access_token"] diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/auth/test_login.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/auth/test_login.py" new file mode 100644 index 000000000..de8d6847d --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/auth/test_login.py" @@ -0,0 +1,14 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_login_success(client: AsyncClient): + # Given + data = {"user_id": 1} + + # When + response = await client.post("/auth/login", json=data) + + # Then + assert response.status_code == status.HTTP_200_OK + assert response.json()["access_token"] is not None diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_create_post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_create_post.py" new file mode 100644 index 000000000..565c20ca6 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_create_post.py" @@ -0,0 +1,43 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_create_post_success(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + data = {"title": "post 1", "content": "some content", "published_at": "2024-04-12T04:33:14.403Z", "published": True} + + # When + response = await client.post("/posts/", json=data, headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_201_CREATED + assert content["id"] is not None + + +async def test_create_post_invalid_payload_fail(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + data = {"content": "some content", "published_at": "2024-04-12T04:33:14.403Z", "published": True} + + # When + response = await client.post("/posts/", json=data, headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert content["detail"][0]["loc"] == ["body", "title"] + + +async def test_create_post_not_authenticated_fail(client: AsyncClient): + # Given + data = {"content": "some content", "published_at": "2024-04-12T04:33:14.403Z", "published": True} + + # When + response = await client.post("/posts/", json=data, headers={}) + + # Then + assert response.status_code == status.HTTP_401_UNAUTHORIZED diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_delete_post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_delete_post.py" new file mode 100644 index 000000000..a6dccae60 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_delete_post.py" @@ -0,0 +1,49 @@ +import pytest_asyncio +from fastapi import status +from httpx import AsyncClient + + +@pytest_asyncio.fixture(autouse=True) +async def populate_posts(db): + from src.schemas.post import PostIn + from src.services.post import PostService + + service = PostService() + await service.create(PostIn(title="post 1", content="some content", published=True)) + await service.create(PostIn(title="post 2", content="some content", published=True)) + await service.create(PostIn(title="post 3", content="some content", published=False)) + + +async def test_delete_post_success(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + post_id = 1 + + # When + response = await client.delete(f"/posts/{post_id}", headers=headers) + + # Then + assert response.status_code == status.HTTP_204_NO_CONTENT + + +async def test_delete_post_not_authenticated_fail(client: AsyncClient): + # Given + post_id = 1 + + # When + response = await client.delete(f"/posts/{post_id}", headers={}) + + # Then + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +async def test_delete_post_not_found_success(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + post_id = 4 + + # When + response = await client.delete(f"/posts/{post_id}", headers=headers) + + # Then + assert response.status_code == status.HTTP_204_NO_CONTENT diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_all.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_all.py" new file mode 100644 index 000000000..799811cbe --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_all.py" @@ -0,0 +1,68 @@ +import pytest +import pytest_asyncio +from fastapi import status +from httpx import AsyncClient + + +@pytest_asyncio.fixture(autouse=True) +async def populate_posts(db): + from src.schemas.post import PostIn + from src.services.post import PostService + + service = PostService() + await service.create(PostIn(title="post 1", content="some content", published=True)) + await service.create(PostIn(title="post 2", content="some content", published=True)) + await service.create(PostIn(title="post 3", content="some content", published=False)) + + +@pytest.mark.parametrize("published,total", [("on", 2), ("off", 1)]) +async def test_read_posts_by_status_success(client: AsyncClient, access_token: str, published: str, total: int): + # Given + params = {"published": published, "limit": 10} + headers = {"Authorization": f"Bearer {access_token}"} + + # When + response = await client.get("/posts/", params=params, headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_200_OK + assert len(content) == total + + +async def test_read_posts_limit_success(client: AsyncClient, access_token: str): + # Given + params = {"published": "on", "limit": 1} + headers = {"Authorization": f"Bearer {access_token}"} + + # When + response = await client.get("/posts/", params=params, headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_200_OK + assert len(content) == 1 + + +async def test_read_posts_not_authenticated_fail(client: AsyncClient): + # Given + params = {"published": "on", "limit": 1} + + # When + response = await client.get("/posts/", params=params, headers={}) + + # Then + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +async def test_read_posts_empty_parameters_fail(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + + # When + response = await client.get("/posts/", params={}, headers=headers) + + # Then + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_post.py" new file mode 100644 index 000000000..7df1ba369 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_read_post.py" @@ -0,0 +1,52 @@ +import pytest_asyncio +from fastapi import status +from httpx import AsyncClient + + +@pytest_asyncio.fixture(autouse=True) +async def populate_posts(db): + from src.schemas.post import PostIn + from src.services.post import PostService + + service = PostService() + await service.create(PostIn(title="post 1", content="some content", published=True)) + await service.create(PostIn(title="post 2", content="some content", published=True)) + await service.create(PostIn(title="post 3", content="some content", published=False)) + + +async def test_read_post_success(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + post_id = 1 + + # When + response = await client.get(f"/posts/{post_id}", headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_200_OK + assert content["id"] == post_id + + +async def test_read_post_not_authenticated_fail(client: AsyncClient): + # Given + post_id = 1 + + # When + response = await client.get(f"/posts/{post_id}", headers={}) + + # Then + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +async def test_read_post_not_found_fail(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + post_id = 4 + + # When + response = await client.get(f"/posts/{post_id}", headers=headers) + + # Then + assert response.status_code == status.HTTP_404_NOT_FOUND diff --git "a/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_update_post.py" "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_update_post.py" new file mode 100644 index 000000000..854c13911 --- /dev/null +++ "b/13 - APIs Ass\303\255ncronas com FastAPI/dio-blog/tests/integration/controllers/post/test_update_post.py" @@ -0,0 +1,54 @@ +import pytest_asyncio +from fastapi import status +from httpx import AsyncClient + + +@pytest_asyncio.fixture(autouse=True) +async def populate_posts(db): + from src.schemas.post import PostIn + from src.services.post import PostService + + service = PostService() + await service.create(PostIn(title="post 1", content="some content", published=True)) + await service.create(PostIn(title="post 2", content="some content", published=True)) + await service.create(PostIn(title="post 3", content="some content", published=False)) + + +async def test_update_post_success(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + data = {"title": "update title post 1"} + post_id = 1 + + # When + response = await client.patch(f"/posts/{post_id}", json=data, headers=headers) + + # Then + content = response.json() + + assert response.status_code == status.HTTP_200_OK + assert content["title"] == data["title"] + + +async def test_update_post_not_authenticated_fail(client: AsyncClient): + # Given + post_id = 1 + + # When + response = await client.patch(f"/posts/{post_id}", headers={}) + + # Then + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + +async def test_update_post_not_found_fail(client: AsyncClient, access_token: str): + # Given + headers = {"Authorization": f"Bearer {access_token}"} + data = {"title": "update title post 4"} + post_id = 4 + + # When + response = await client.patch(f"/posts/{post_id}", json=data, headers=headers) + + # Then + assert response.status_code == status.HTTP_404_NOT_FOUND From cd039d694e47b9907695d9e410a4a3aa929a68a5 Mon Sep 17 00:00:00 2001 From: PLM4 Date: Wed, 15 Oct 2025 17:12:04 -0300 Subject: [PATCH 13/16] feat[]: Desafio v2, adicionando usuario, conta e separando funcoes --- 00 - Fundamentos/desafio.py | 303 +++++++++++++++++++++++++++++++----- 1 file changed, 261 insertions(+), 42 deletions(-) diff --git a/00 - Fundamentos/desafio.py b/00 - Fundamentos/desafio.py index c2179f64d..c6dd9b91f 100644 --- a/00 - Fundamentos/desafio.py +++ b/00 - Fundamentos/desafio.py @@ -1,66 +1,285 @@ -menu = """ - -[d] Depositar -[s] Sacar -[e] Extrato -[q] Sair - -=> """ - +LIMITE_SAQUES = 3 +AGENCIA = "0001" saldo = 0 limite = 500 extrato = "" numero_saques = 0 -LIMITE_SAQUES = 3 +enderecos = {} +usuarios = {} +contas_corrente = [] -while True: +def menu(): + return """ + + [1] Cadastrar Usuário + [2] Mostrar Usuários + [3] Criar conta + [4] Listar contas + [5] Depositar + [6] Sacar + [7] Extrato + [8] Sair + + => """ + +def menu_confirma_usuario(): + return """ + + [n] Nome + [d] Data de nascimento + [c] Cpf + [e] Endereco + [p] Pronto + + => """ + +def confirma_nome_usuario(): + while True: + nome = input("Digite o nome do usuario:") + + confirmacao = input("Tem certeza que é esse o nome?[s/n]: ").lower() + + if confirmacao == "s": + return nome + else: + return "Nome não cadastrado, tente novamente." + +def confirma_data_nascimento(): + while True: + data_nascimento = input("Digite a data de nascimento do usuario [DD/MM/AAAA]: ") - opcao = input(menu) + confirmacao = input("Tem certeza que é essa a sua data de nascimento?[s/n]: ").lower() + + if confirmacao == "s": + return data_nascimento + else: + return "Data de nascimento não cadastrada, tente novamente" + +def confirma_cpf_usuario(): + while True: + cpf = input("Digite o cpf do usuario (apenas números): ").strip() - if opcao == "d": - valor = float(input("Informe o valor do depósito: ")) + if len(cpf) != 11 or not cpf.isdigit(): + print("Cpf deve conter exatamento 11 números. tente novamente") + continue + elif cpf in usuarios: + print("Esse cpf já é cadastrado. Tente novamente.") + continue - if valor > 0: - saldo += valor - extrato += f"Depósito: R$ {valor:.2f}\n" + confirmacao = input("Tem certeza que é esse o seu cpf?[s/n]: ").lower() + if confirmacao == "s": + return cpf else: - print("Operação falhou! O valor informado é inválido.") + return "Cpf não cadastrada, tente novamente." + +def format_endereco(logradouro, numero, bairro, cidade_sigla, estado): + return { + "logradouro": logradouro, + "numero": numero, + "bairro": bairro, + "cidade_sigla": cidade_sigla, + "estado": estado + } + +def confirma_endereco(): + print("\n--- Cadastro de Endereço ---") + logradouro = input("Logradouro (rua, avenida, etc.): ") + numero = input("Número: ") + bairro = input("Bairro: ") + cidade_sigla = input("Cidade/Sigla: ") + estado = input("Estado: ") + + while True: + confirmacao = input("Confirma os dados do endereço? [s/n]: ").lower() + if confirmacao == "s": + return format_endereco(logradouro, numero, bairro, cidade_sigla, estado) + else: + print("Endereço não confirmado, vamos recomeçar:") + logradouro = input("Logradouro: ") + numero = input("Número: ") + bairro = input("Bairro: ") + cidade_sigla = input("Cidade/Sigla: ") + estado = input("Estado: ") + - elif opcao == "s": - valor = float(input("Informe o valor do saque: ")) +def cadastrar_usuario(nome, data_nascimento, cpf, endereco): + usuarios[cpf] = { + "nome": nome, + "data_nascimento": data_nascimento, + "endereco": endereco + } - excedeu_saldo = valor > saldo + return "Usuario cadastrado com sucesso!" + - excedeu_limite = valor > limite +def menu_cadastro_usuario(): + dados_usuario = {} - excedeu_saques = numero_saques >= LIMITE_SAQUES + while True: + opc = input(menu_confirma_usuario()).lower() - if excedeu_saldo: - print("Operação falhou! Você não tem saldo suficiente.") + match opc: + case "n": + dados_usuario['nome'] = confirma_nome_usuario() + print(f"Nome definido: {dados_usuario['nome']}") + case "d": + dados_usuario['data_nascimento'] = confirma_data_nascimento() + print(f"Data de nascimento definido: {dados_usuario['data_nascimento']}") + case "c": + dados_usuario['cpf'] = confirma_cpf_usuario() + print(f"CPF definido: {dados_usuario['cpf']}") + case "e": + dados_usuario['endereco'] = confirma_endereco() + print(f"Endereco definido: {dados_usuario['endereco']}") + case "p": + # Verifica se todos os dados foram preenchidos + campos_obrigatorios = ['nome', 'data_nascimento', 'cpf', 'endereco'] + if all(campo in dados_usuario for campo in campos_obrigatorios): + resultado = cadastrar_usuario( + dados_usuario['nome'], + dados_usuario['data_nascimento'], + dados_usuario['cpf'], + dados_usuario['endereco'] + ) + print(resultado) + break + else: + print("Preencha todos os dados antes de finalizar!") + campos_faltantes = [campo for campo in campos_obrigatorios if campo not in dados_usuario] + print(f"Campos faltantes: {', '.join(campos_faltantes)}") + + case _: + print("Opção inválida! Tente novamente.") - elif excedeu_limite: - print("Operação falhou! O valor do saque excede o limite.") +def confirma_cpf_conta(): + while True: + cpf = input("Digite o cpf do usuario (apenas números): ").strip() - elif excedeu_saques: - print("Operação falhou! Número máximo de saques excedido.") + if len(cpf) != 11 or not cpf.isdigit(): + print("Cpf deve conter exatamento 11 números. tente novamente") + continue + elif cpf not in usuarios: + print("Esse cpf não exitse. Cadastre um novo usuário ou tente novamente.") + continue - elif valor > 0: - saldo -= valor - extrato += f"Saque: R$ {valor:.2f}\n" - numero_saques += 1 + confirmacao = input(f"Confirma que o {usuarios[cpf]['nome']} é o titular?[s/n]: ").lower() + if confirmacao == "s": + return cpf else: - print("Operação falhou! O valor informado é inválido.") + return "Cpf não cadastrada, tente novamente." + + +def abrir_conta(agencia, numero_conta, usuarios): + cpf = confirma_cpf_conta() + + if cpf is None: + return None + + print("\nConta criada com sucesso!") + return { + "agencia": agencia, + "numero_conta": numero_conta, + "usuario": usuarios[cpf], + "cpf": cpf + } + - elif opcao == "e": - print("\n================ EXTRATO ================") - print("Não foram realizadas movimentações." if not extrato else extrato) - print(f"\nSaldo: R$ {saldo:.2f}") - print("==========================================") +def listar_contas(contas_corrente): + if not contas_corrente: + print("Nenhuma conta cadastrada.") + return + + for i, conta in enumerate(contas_corrente, 1): + print(f""" + Conta {i}: + Agência:\t{conta['agencia']} + C/C:\t\t{conta['numero_conta']} + Titular:\t{conta['usuario']['nome']} + """) - elif opcao == "q": - break +def deposito(valor, saldo, extrato): + if valor > 0: + saldo += valor + extrato += f"Depósito: R$ {valor:.2f}\n" + print(f"Depósito de R$ {valor:.2f} realizado com sucesso!") + return saldo, extrato else: - print("Operação inválida, por favor selecione novamente a operação desejada.") + print("Operação falhou! O valor informado é inválido.") + return saldo, extrato + +def extrair(extrato, saldo): + print("\n================ EXTRATO ================") + print("Não foram realizadas movimentações." if not extrato else extrato) + print(f"\nSaldo: R$ {saldo:.2f}") + print("==========================================") + +def saque(*, valor, saldo=saldo, extrato=extrato, numero_saques=numero_saques): + excedeu_saldo = valor > saldo + excedeu_limite = valor > limite + excedeu_saques = numero_saques >= LIMITE_SAQUES + + if excedeu_saldo: + print("Operação falhou! Você não tem saldo suficiente.") + return saldo, extrato, numero_saques + elif excedeu_limite: + print("Operação falhou! O valor do saque excede o limite.") + return saldo, extrato, numero_saques + elif excedeu_saques: + print("Operação falhou! Número máximo de saques excedido.") + return saldo, extrato, numero_saques + elif valor > 0: + saldo -= valor + extrato += f"Saque: R$ {valor:.2f}\n" + numero_saques += 1 + print(f"Saque de R$ {valor:.2f} realizado com sucesso!") + return saldo, extrato, numero_saques + else: + print("Operação falhou! O valor informado é inválido.") + return saldo, extrato, numero_saques + +while True: + opc = input(menu()) + + match opc: + case "1": + menu_cadastro_usuario() + case "2": + if usuarios: + print("########-USUARIOS CADASTRADOS-########") + for cpf, dados in usuarios.items(): + print(f"CPF: {cpf}") + print(f"Nome: {dados['nome']}") + print(f"Data de nascimento: {dados['data_nascimento']}") + print(f"Endereco: {dados['endereco']['logradouro']}, {dados['endereco']['numero']} - {dados['endereco']['bairro']} - {dados['endereco']['cidade_sigla']} {dados['endereco']['estado']}") + print("-" * 30) + else: + print("Nenhum usuario encontrado.") + case "3": + numero_conta = len(contas_corrente) + 1 + conta = abrir_conta(AGENCIA, numero_conta, usuarios) + contas_corrente.append(conta) + case "4": + if contas_corrente: + listar_contas(contas_corrente) + else: + print("\nNão existem contas cadastradas.") + case "5": + valor = float(input("Informe o valor do depósito: ")) + saldo, extrato = deposito(valor, saldo, extrato) + case "6": + valor = float(input("Informe o valor do saque: ")) + saldo, extrato, numero_saques = saque(saldo=saldo, + valor=valor, + extrato=extrato, + numero_saques=numero_saques, + limite_saques=LIMITE_SAQUES,) + case "7": + extrair(extrato, saldo) + case "8": + print("Saindo do sistema...") + break + case _: + print("Operação inválida, por favor selecione novamente. a operação desejada.") \ No newline at end of file From e733b1449be5c417c5241cd16c12b48eae95cabf Mon Sep 17 00:00:00 2001 From: PLM4 Date: Fri, 7 Nov 2025 23:47:03 -0300 Subject: [PATCH 14/16] feat[]: concluindo desafio um e dois da criacao de classes para o sistema bancario (POO) --- .../10 - desafio/desafio_v1.py" | 80 ++++---- .../10 - desafio/desafio_v2.py" | 191 ++++++++---------- 2 files changed, 121 insertions(+), 150 deletions(-) diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" index 687af2268..34e8c18fa 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" @@ -1,8 +1,7 @@ -from abc import ABC, abstractclassmethod, abstractproperty -from datetime import datetime +from abc import ABC, abstractmethod, abstractproperty +import datetime - -class Cliente: +class Cliente(): def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -13,7 +12,6 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) - class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -21,35 +19,34 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf - -class Conta: +class Conta(): def __init__(self, numero, cliente): - self._saldo = 0 self._numero = numero - self._agencia = "0001" self._cliente = cliente + self._agencia = "0001" + self._saldo = 0 self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -59,30 +56,29 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + print("\n@@@ Operação falhou! Saldo insuficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False - return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Depósito realizado com sucesso! ===") + print("\n=== Déposito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True - - class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) self.limite = limite @@ -90,48 +86,46 @@ def __init__(self, numero, cliente, limite=500, limite_saques=3): def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + [transacao for transacao in self.historico. + transacoes if transacao['tipo'] == "saque"] ) excedeu_limite = valor > self.limite excedeu_saques = numero_saques >= self.limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t\t{self.numero} - Titular:\t{self.cliente.nome} + Agência:\t{self.agencia} + C/C:\t{self.numero} + Titular:\t{self.cliente.nome} + Saldo:\tR$ {self.saldo:.2f} """ - -class Historico: +class Historico(): def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append( - { - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), - } - ) - + self._transacoes.append({ + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") + }) class Transacao(ABC): @property @@ -139,11 +133,10 @@ class Transacao(ABC): def valor(self): pass - @abstractclassmethod + @abstractmethod def registrar(self, conta): pass - class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -151,14 +144,13 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -166,9 +158,9 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: - conta.historico.adicionar_transacao(self) + conta.historico.adicionar_transacao(self) \ No newline at end of file diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" index 96822be13..78dde3390 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" @@ -1,9 +1,8 @@ +from abc import ABC, abstractmethod, abstractproperty +from datetime import datetime import textwrap -from abc import ABC, abstractclassmethod, abstractproperty -from datetime import datetime - -class Cliente: +class Cliente(): def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -14,7 +13,6 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) - class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -22,35 +20,34 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf - -class Conta: +class Conta(): def __init__(self, numero, cliente): - self._saldo = 0 self._numero = numero - self._agencia = "0001" self._cliente = cliente + self._agencia = "0001" + self._saldo = 0 self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -60,79 +57,75 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + print("\n@@@ Operação falhou! Saldo insuficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False - return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Depósito realizado com sucesso! ===") + print("\n=== Déposito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True - - + class ContaCorrente(Conta): def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) - self._limite = limite - self._limite_saques = limite_saques + self.limite = limite + self.limite_saques = limite_saques def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + [transacao for transacao in self.historico. + transacoes if transacao['tipo'] == "saque"] ) - excedeu_limite = valor > self._limite - excedeu_saques = numero_saques >= self._limite_saques + excedeu_limite = valor > self.limite + excedeu_saques = numero_saques >= self.limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t\t{self.numero} - Titular:\t{self.cliente.nome} + Agência:\t{self.agencia} + C/C:\t{self.numero} + Titular:\t{self.cliente.nome} + Saldo:\tR$ {self.saldo:.2f} """ - - -class Historico: +class Historico(): def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append( - { - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), - } - ) - + self._transacoes.append({ + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") + }) class Transacao(ABC): @property @@ -140,11 +133,10 @@ class Transacao(ABC): def valor(self): pass - @abstractclassmethod + @abstractmethod def registrar(self, conta): pass - class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -152,14 +144,13 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -167,42 +158,40 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - def menu(): menu = """\n - ================ MENU ================ - [d]\tDepositar - [s]\tSacar - [e]\tExtrato - [nc]\tNova conta - [lc]\tListar contas - [nu]\tNovo usuário - [q]\tSair - => """ - return input(textwrap.dedent(menu)) + ======================= + | MENU DE OPÇÕES | + ======================= + [1]\tDepositar + [2]\tSacar + [3]\tExtrato + [4]\tNovo Cliente + [5]\tNova Conta + [6]\tListar Contas + [0]\tSair + =>""" + return input(textwrap.dedent(menu)) def filtrar_cliente(cpf, clientes): clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] return clientes_filtrados[0] if clientes_filtrados else None - def recuperar_conta_cliente(cliente): if not cliente.contas: - print("\n@@@ Cliente não possui conta! @@@") - return - - # FIXME: não permite cliente escolher a conta + print("\n@@@ O cliente não possui conta! @@@") + return + return cliente.contas[0] - def depositar(clientes): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) @@ -210,16 +199,15 @@ def depositar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do depósito: ")) - transacao = Deposito(valor) + transacao = Deposito(valor) + conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) - + cliente.realizar_transacao(conta, transacao) def sacar(clientes): cpf = input("Informe o CPF do cliente: ") @@ -228,16 +216,15 @@ def sacar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) - + cliente.realizar_transacao(conta, transacao) def exibir_extrato(clientes): cpf = input("Informe o CPF do cliente: ") @@ -248,64 +235,60 @@ def exibir_extrato(clientes): return conta = recuperar_conta_cliente(cliente) + if not conta: return - + print("\n================ EXTRATO ================") transacoes = conta.historico.transacoes extrato = "" if not transacoes: - extrato = "Não foram realizadas movimentações." + extrato += "\nNão foram realizadas movimentações." else: for transacao in transacoes: - extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + extrato += f"\n{transacao['tipo']}\tR$ {transacao['valor']:.2f}\t{transacao['data']}" print(extrato) - print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") - print("==========================================") - + print(f"\nSaldo:\tR$ {conta.saldo:.2f}") def criar_cliente(clientes): - cpf = input("Informe o CPF (somente número): ") + cpf = input("Informe o cpf (somente numeros): ") cliente = filtrar_cliente(cpf, clientes) if cliente: - print("\n@@@ Já existe cliente com esse CPF! @@@") + print("\n @@@ Já existe esse cliente com esse CPF! @@@") return - nome = input("Informe o nome completo: ") - data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") - endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + nome = input("Informe o nome completo do cliente: ") + data_nascimento = input("Informe a data de nascimento (dd/mm/aaaa): ") + endereco = input("Informe o endereço (logradouro, número, bairro, cidade/sigla estado): ") cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) clientes.append(cliente) - print("\n=== Cliente criado com sucesso! ===") - + print("\n === Cliente criado com sucesso! ===") def criar_conta(numero_conta, clientes, contas): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) if not cliente: - print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + print("\n@@@ Cliente não encontrado! @@@") return conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta) contas.append(conta) cliente.contas.append(conta) - print("\n=== Conta criada com sucesso! ===") - + print("\n === Conta criada com sucesso! ===") def listar_contas(contas): for conta in contas: - print("=" * 100) + print("-" * 100) print(textwrap.dedent(str(conta))) - def main(): clientes = [] contas = [] @@ -313,30 +296,26 @@ def main(): while True: opcao = menu() - if opcao == "d": + if opcao == "1": depositar(clientes) - - elif opcao == "s": + elif opcao == "2": sacar(clientes) - - elif opcao == "e": + elif opcao == "3": exibir_extrato(clientes) - - elif opcao == "nu": + elif opcao == "4": criar_cliente(clientes) - - elif opcao == "nc": + elif opcao == "5": numero_conta = len(contas) + 1 criar_conta(numero_conta, clientes, contas) - - elif opcao == "lc": + elif opcao == "6": listar_contas(contas) - - elif opcao == "q": + elif opcao == "0": + print(""" + ============================================ + | Obrigado por utilizar o nosso sistema! | + ============================================""") break - else: print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") - -main() +main() \ No newline at end of file From b28b2234387d0033f851f744279a29a0a42ce655 Mon Sep 17 00:00:00 2001 From: PLM4 Date: Sat, 8 Nov 2025 00:12:23 -0300 Subject: [PATCH 15/16] Revert "feat[]: concluindo desafio um e dois da criacao de classes para o sistema bancario (POO)" This reverts commit e733b1449be5c417c5241cd16c12b48eae95cabf. --- .../10 - desafio/desafio_v1.py" | 80 ++++---- .../10 - desafio/desafio_v2.py" | 191 ++++++++++-------- 2 files changed, 150 insertions(+), 121 deletions(-) diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" index 34e8c18fa..687af2268 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" @@ -1,7 +1,8 @@ -from abc import ABC, abstractmethod, abstractproperty -import datetime +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime -class Cliente(): + +class Cliente: def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -12,6 +13,7 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) + class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -19,34 +21,35 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf -class Conta(): + +class Conta: def __init__(self, numero, cliente): + self._saldo = 0 self._numero = numero - self._cliente = cliente self._agencia = "0001" - self._saldo = 0 + self._cliente = cliente self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -56,29 +59,30 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Saldo insuficiente. @@@") + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") - return False + return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Déposito realizado com sucesso! ===") + print("\n=== Depósito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True -class ContaCorrente(Conta): + +class ContaCorrente(Conta): def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) self.limite = limite @@ -86,46 +90,48 @@ def __init__(self, numero, cliente, limite=500, limite_saques=3): def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico. - transacoes if transacao['tipo'] == "saque"] + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] ) excedeu_limite = valor > self.limite excedeu_saques = numero_saques >= self.limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t{self.numero} - Titular:\t{self.cliente.nome} - Saldo:\tR$ {self.saldo:.2f} + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} """ -class Historico(): + +class Historico: def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append({ - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") - }) + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), + } + ) + class Transacao(ABC): @property @@ -133,10 +139,11 @@ class Transacao(ABC): def valor(self): pass - @abstractmethod + @abstractclassmethod def registrar(self, conta): pass + class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -144,13 +151,14 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) + class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -158,9 +166,9 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: - conta.historico.adicionar_transacao(self) \ No newline at end of file + conta.historico.adicionar_transacao(self) diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" index 78dde3390..96822be13 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" @@ -1,8 +1,9 @@ -from abc import ABC, abstractmethod, abstractproperty -from datetime import datetime import textwrap +from abc import ABC, abstractclassmethod, abstractproperty +from datetime import datetime -class Cliente(): + +class Cliente: def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -13,6 +14,7 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) + class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -20,34 +22,35 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf -class Conta(): + +class Conta: def __init__(self, numero, cliente): + self._saldo = 0 self._numero = numero - self._cliente = cliente self._agencia = "0001" - self._saldo = 0 + self._cliente = cliente self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -57,75 +60,79 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Saldo insuficiente. @@@") + print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") - return False + return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Déposito realizado com sucesso! ===") + print("\n=== Depósito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True - + + class ContaCorrente(Conta): def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) - self.limite = limite - self.limite_saques = limite_saques + self._limite = limite + self._limite_saques = limite_saques def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico. - transacoes if transacao['tipo'] == "saque"] + [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] ) - excedeu_limite = valor > self.limite - excedeu_saques = numero_saques >= self.limite_saques + excedeu_limite = valor > self._limite + excedeu_saques = numero_saques >= self._limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") + print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t{self.numero} - Titular:\t{self.cliente.nome} - Saldo:\tR$ {self.saldo:.2f} + Agência:\t{self.agencia} + C/C:\t\t{self.numero} + Titular:\t{self.cliente.nome} """ -class Historico(): + + +class Historico: def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append({ - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") - }) + self._transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), + } + ) + class Transacao(ABC): @property @@ -133,10 +140,11 @@ class Transacao(ABC): def valor(self): pass - @abstractmethod + @abstractclassmethod def registrar(self, conta): pass + class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -144,13 +152,14 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) + class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -158,40 +167,42 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) + def menu(): menu = """\n - ======================= - | MENU DE OPÇÕES | - ======================= - [1]\tDepositar - [2]\tSacar - [3]\tExtrato - [4]\tNovo Cliente - [5]\tNova Conta - [6]\tListar Contas - [0]\tSair - =>""" - + ================ MENU ================ + [d]\tDepositar + [s]\tSacar + [e]\tExtrato + [nc]\tNova conta + [lc]\tListar contas + [nu]\tNovo usuário + [q]\tSair + => """ return input(textwrap.dedent(menu)) + def filtrar_cliente(cpf, clientes): clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] return clientes_filtrados[0] if clientes_filtrados else None + def recuperar_conta_cliente(cliente): if not cliente.contas: - print("\n@@@ O cliente não possui conta! @@@") - return - + print("\n@@@ Cliente não possui conta! @@@") + return + + # FIXME: não permite cliente escolher a conta return cliente.contas[0] + def depositar(clientes): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) @@ -199,15 +210,16 @@ def depositar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do depósito: ")) + valor = float(input("Informe o valor do depósito: ")) transacao = Deposito(valor) - + conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) + cliente.realizar_transacao(conta, transacao) + def sacar(clientes): cpf = input("Informe o CPF do cliente: ") @@ -216,15 +228,16 @@ def sacar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do saque: ")) + valor = float(input("Informe o valor do saque: ")) transacao = Saque(valor) conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) + cliente.realizar_transacao(conta, transacao) + def exibir_extrato(clientes): cpf = input("Informe o CPF do cliente: ") @@ -235,60 +248,64 @@ def exibir_extrato(clientes): return conta = recuperar_conta_cliente(cliente) - if not conta: return - + print("\n================ EXTRATO ================") transacoes = conta.historico.transacoes extrato = "" if not transacoes: - extrato += "\nNão foram realizadas movimentações." + extrato = "Não foram realizadas movimentações." else: for transacao in transacoes: - extrato += f"\n{transacao['tipo']}\tR$ {transacao['valor']:.2f}\t{transacao['data']}" + extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" print(extrato) - print(f"\nSaldo:\tR$ {conta.saldo:.2f}") + print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") + print("==========================================") + def criar_cliente(clientes): - cpf = input("Informe o cpf (somente numeros): ") + cpf = input("Informe o CPF (somente número): ") cliente = filtrar_cliente(cpf, clientes) if cliente: - print("\n @@@ Já existe esse cliente com esse CPF! @@@") + print("\n@@@ Já existe cliente com esse CPF! @@@") return - nome = input("Informe o nome completo do cliente: ") - data_nascimento = input("Informe a data de nascimento (dd/mm/aaaa): ") - endereco = input("Informe o endereço (logradouro, número, bairro, cidade/sigla estado): ") + nome = input("Informe o nome completo: ") + data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") + endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) clientes.append(cliente) - print("\n === Cliente criado com sucesso! ===") + print("\n=== Cliente criado com sucesso! ===") + def criar_conta(numero_conta, clientes, contas): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) if not cliente: - print("\n@@@ Cliente não encontrado! @@@") + print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") return conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta) contas.append(conta) cliente.contas.append(conta) - print("\n === Conta criada com sucesso! ===") + print("\n=== Conta criada com sucesso! ===") + def listar_contas(contas): for conta in contas: - print("-" * 100) + print("=" * 100) print(textwrap.dedent(str(conta))) + def main(): clientes = [] contas = [] @@ -296,26 +313,30 @@ def main(): while True: opcao = menu() - if opcao == "1": + if opcao == "d": depositar(clientes) - elif opcao == "2": + + elif opcao == "s": sacar(clientes) - elif opcao == "3": + + elif opcao == "e": exibir_extrato(clientes) - elif opcao == "4": + + elif opcao == "nu": criar_cliente(clientes) - elif opcao == "5": + + elif opcao == "nc": numero_conta = len(contas) + 1 criar_conta(numero_conta, clientes, contas) - elif opcao == "6": + + elif opcao == "lc": listar_contas(contas) - elif opcao == "0": - print(""" - ============================================ - | Obrigado por utilizar o nosso sistema! | - ============================================""") + + elif opcao == "q": break + else: print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") -main() \ No newline at end of file + +main() From 38e732bc9b25130292276172d8bcfe73d89545b9 Mon Sep 17 00:00:00 2001 From: PLM4 Date: Sat, 8 Nov 2025 16:13:12 -0300 Subject: [PATCH 16/16] feat[]: concluindo desafio um e dois da criacao de classes para o sistema bancario (POO) --- .../10 - desafio/desafio_v1.py" | 80 ++++---- .../10 - desafio/desafio_v2.py" | 191 ++++++++---------- 2 files changed, 121 insertions(+), 150 deletions(-) diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" index 687af2268..34e8c18fa 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v1.py" @@ -1,8 +1,7 @@ -from abc import ABC, abstractclassmethod, abstractproperty -from datetime import datetime +from abc import ABC, abstractmethod, abstractproperty +import datetime - -class Cliente: +class Cliente(): def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -13,7 +12,6 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) - class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -21,35 +19,34 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf - -class Conta: +class Conta(): def __init__(self, numero, cliente): - self._saldo = 0 self._numero = numero - self._agencia = "0001" self._cliente = cliente + self._agencia = "0001" + self._saldo = 0 self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -59,30 +56,29 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + print("\n@@@ Operação falhou! Saldo insuficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False - return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Depósito realizado com sucesso! ===") + print("\n=== Déposito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True - - class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) self.limite = limite @@ -90,48 +86,46 @@ def __init__(self, numero, cliente, limite=500, limite_saques=3): def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + [transacao for transacao in self.historico. + transacoes if transacao['tipo'] == "saque"] ) excedeu_limite = valor > self.limite excedeu_saques = numero_saques >= self.limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t\t{self.numero} - Titular:\t{self.cliente.nome} + Agência:\t{self.agencia} + C/C:\t{self.numero} + Titular:\t{self.cliente.nome} + Saldo:\tR$ {self.saldo:.2f} """ - -class Historico: +class Historico(): def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append( - { - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), - } - ) - + self._transacoes.append({ + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") + }) class Transacao(ABC): @property @@ -139,11 +133,10 @@ class Transacao(ABC): def valor(self): pass - @abstractclassmethod + @abstractmethod def registrar(self, conta): pass - class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -151,14 +144,13 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -166,9 +158,9 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: - conta.historico.adicionar_transacao(self) + conta.historico.adicionar_transacao(self) \ No newline at end of file diff --git "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" index 96822be13..78dde3390 100644 --- "a/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" +++ "b/02 - Programa\303\247\303\243o Orientada a Objetos/10 - desafio/desafio_v2.py" @@ -1,9 +1,8 @@ +from abc import ABC, abstractmethod, abstractproperty +from datetime import datetime import textwrap -from abc import ABC, abstractclassmethod, abstractproperty -from datetime import datetime - -class Cliente: +class Cliente(): def __init__(self, endereco): self.endereco = endereco self.contas = [] @@ -14,7 +13,6 @@ def realizar_transacao(self, conta, transacao): def adicionar_conta(self, conta): self.contas.append(conta) - class PessoaFisica(Cliente): def __init__(self, nome, data_nascimento, cpf, endereco): super().__init__(endereco) @@ -22,35 +20,34 @@ def __init__(self, nome, data_nascimento, cpf, endereco): self.data_nascimento = data_nascimento self.cpf = cpf - -class Conta: +class Conta(): def __init__(self, numero, cliente): - self._saldo = 0 self._numero = numero - self._agencia = "0001" self._cliente = cliente + self._agencia = "0001" + self._saldo = 0 self._historico = Historico() @classmethod def nova_conta(cls, cliente, numero): return cls(numero, cliente) - + @property def saldo(self): return self._saldo - + @property def numero(self): return self._numero - + @property def agencia(self): return self._agencia - + @property def cliente(self): return self._cliente - + @property def historico(self): return self._historico @@ -60,79 +57,75 @@ def sacar(self, valor): excedeu_saldo = valor > saldo if excedeu_saldo: - print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") + print("\n@@@ Operação falhou! Saldo insuficiente. @@@") elif valor > 0: self._saldo -= valor print("\n=== Saque realizado com sucesso! ===") return True - + else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + return False - return False def depositar(self, valor): if valor > 0: self._saldo += valor - print("\n=== Depósito realizado com sucesso! ===") + print("\n=== Déposito realizado com sucesso! ===") else: print("\n@@@ Operação falhou! O valor informado é inválido. @@@") return False return True - - + class ContaCorrente(Conta): def __init__(self, numero, cliente, limite=500, limite_saques=3): super().__init__(numero, cliente) - self._limite = limite - self._limite_saques = limite_saques + self.limite = limite + self.limite_saques = limite_saques def sacar(self, valor): numero_saques = len( - [transacao for transacao in self.historico.transacoes if transacao["tipo"] == Saque.__name__] + [transacao for transacao in self.historico. + transacoes if transacao['tipo'] == "saque"] ) - excedeu_limite = valor > self._limite - excedeu_saques = numero_saques >= self._limite_saques + excedeu_limite = valor > self.limite + excedeu_saques = numero_saques >= self.limite_saques if excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + print("\n@@@ Operação falhou! O valor do saque excedeu o limite! @@@") elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + print("\n@@@ Operação falhou! Número máximo de saques excedido! @@@") else: return super().sacar(valor) - + return False def __str__(self): return f"""\ - Agência:\t{self.agencia} - C/C:\t\t{self.numero} - Titular:\t{self.cliente.nome} + Agência:\t{self.agencia} + C/C:\t{self.numero} + Titular:\t{self.cliente.nome} + Saldo:\tR$ {self.saldo:.2f} """ - - -class Historico: +class Historico(): def __init__(self): self._transacoes = [] @property def transacoes(self): return self._transacoes - + def adicionar_transacao(self, transacao): - self._transacoes.append( - { - "tipo": transacao.__class__.__name__, - "valor": transacao.valor, - "data": datetime.now().strftime("%d-%m-%Y %H:%M:%s"), - } - ) - + self._transacoes.append({ + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d/%m/%Y/%H:%M:%S") + }) class Transacao(ABC): @property @@ -140,11 +133,10 @@ class Transacao(ABC): def valor(self): pass - @abstractclassmethod + @abstractmethod def registrar(self, conta): pass - class Saque(Transacao): def __init__(self, valor): self._valor = valor @@ -152,14 +144,13 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.sacar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - class Deposito(Transacao): def __init__(self, valor): self._valor = valor @@ -167,42 +158,40 @@ def __init__(self, valor): @property def valor(self): return self._valor - + def registrar(self, conta): sucesso_transacao = conta.depositar(self.valor) if sucesso_transacao: conta.historico.adicionar_transacao(self) - def menu(): menu = """\n - ================ MENU ================ - [d]\tDepositar - [s]\tSacar - [e]\tExtrato - [nc]\tNova conta - [lc]\tListar contas - [nu]\tNovo usuário - [q]\tSair - => """ - return input(textwrap.dedent(menu)) + ======================= + | MENU DE OPÇÕES | + ======================= + [1]\tDepositar + [2]\tSacar + [3]\tExtrato + [4]\tNovo Cliente + [5]\tNova Conta + [6]\tListar Contas + [0]\tSair + =>""" + return input(textwrap.dedent(menu)) def filtrar_cliente(cpf, clientes): clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] return clientes_filtrados[0] if clientes_filtrados else None - def recuperar_conta_cliente(cliente): if not cliente.contas: - print("\n@@@ Cliente não possui conta! @@@") - return - - # FIXME: não permite cliente escolher a conta + print("\n@@@ O cliente não possui conta! @@@") + return + return cliente.contas[0] - def depositar(clientes): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) @@ -210,16 +199,15 @@ def depositar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do depósito: ")) - transacao = Deposito(valor) + transacao = Deposito(valor) + conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) - + cliente.realizar_transacao(conta, transacao) def sacar(clientes): cpf = input("Informe o CPF do cliente: ") @@ -228,16 +216,15 @@ def sacar(clientes): if not cliente: print("\n@@@ Cliente não encontrado! @@@") return - valor = float(input("Informe o valor do saque: ")) + transacao = Saque(valor) conta = recuperar_conta_cliente(cliente) if not conta: return - cliente.realizar_transacao(conta, transacao) - + cliente.realizar_transacao(conta, transacao) def exibir_extrato(clientes): cpf = input("Informe o CPF do cliente: ") @@ -248,64 +235,60 @@ def exibir_extrato(clientes): return conta = recuperar_conta_cliente(cliente) + if not conta: return - + print("\n================ EXTRATO ================") transacoes = conta.historico.transacoes extrato = "" if not transacoes: - extrato = "Não foram realizadas movimentações." + extrato += "\nNão foram realizadas movimentações." else: for transacao in transacoes: - extrato += f"\n{transacao['tipo']}:\n\tR$ {transacao['valor']:.2f}" + extrato += f"\n{transacao['tipo']}\tR$ {transacao['valor']:.2f}\t{transacao['data']}" print(extrato) - print(f"\nSaldo:\n\tR$ {conta.saldo:.2f}") - print("==========================================") - + print(f"\nSaldo:\tR$ {conta.saldo:.2f}") def criar_cliente(clientes): - cpf = input("Informe o CPF (somente número): ") + cpf = input("Informe o cpf (somente numeros): ") cliente = filtrar_cliente(cpf, clientes) if cliente: - print("\n@@@ Já existe cliente com esse CPF! @@@") + print("\n @@@ Já existe esse cliente com esse CPF! @@@") return - nome = input("Informe o nome completo: ") - data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") - endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + nome = input("Informe o nome completo do cliente: ") + data_nascimento = input("Informe a data de nascimento (dd/mm/aaaa): ") + endereco = input("Informe o endereço (logradouro, número, bairro, cidade/sigla estado): ") cliente = PessoaFisica(nome=nome, data_nascimento=data_nascimento, cpf=cpf, endereco=endereco) clientes.append(cliente) - print("\n=== Cliente criado com sucesso! ===") - + print("\n === Cliente criado com sucesso! ===") def criar_conta(numero_conta, clientes, contas): cpf = input("Informe o CPF do cliente: ") cliente = filtrar_cliente(cpf, clientes) if not cliente: - print("\n@@@ Cliente não encontrado, fluxo de criação de conta encerrado! @@@") + print("\n@@@ Cliente não encontrado! @@@") return conta = ContaCorrente.nova_conta(cliente=cliente, numero=numero_conta) contas.append(conta) cliente.contas.append(conta) - print("\n=== Conta criada com sucesso! ===") - + print("\n === Conta criada com sucesso! ===") def listar_contas(contas): for conta in contas: - print("=" * 100) + print("-" * 100) print(textwrap.dedent(str(conta))) - def main(): clientes = [] contas = [] @@ -313,30 +296,26 @@ def main(): while True: opcao = menu() - if opcao == "d": + if opcao == "1": depositar(clientes) - - elif opcao == "s": + elif opcao == "2": sacar(clientes) - - elif opcao == "e": + elif opcao == "3": exibir_extrato(clientes) - - elif opcao == "nu": + elif opcao == "4": criar_cliente(clientes) - - elif opcao == "nc": + elif opcao == "5": numero_conta = len(contas) + 1 criar_conta(numero_conta, clientes, contas) - - elif opcao == "lc": + elif opcao == "6": listar_contas(contas) - - elif opcao == "q": + elif opcao == "0": + print(""" + ============================================ + | Obrigado por utilizar o nosso sistema! | + ============================================""") break - else: print("\n@@@ Operação inválida, por favor selecione novamente a operação desejada. @@@") - -main() +main() \ No newline at end of file