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..c6dd9b91f --- /dev/null +++ b/00 - Fundamentos/desafio.py @@ -0,0 +1,285 @@ +LIMITE_SAQUES = 3 +AGENCIA = "0001" +saldo = 0 +limite = 500 +extrato = "" +numero_saques = 0 +enderecos = {} +usuarios = {} +contas_corrente = [] + +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]: ") + + 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 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 + + confirmacao = input("Tem certeza que é esse o seu cpf?[s/n]: ").lower() + + if confirmacao == "s": + return cpf + else: + 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: ") + + +def cadastrar_usuario(nome, data_nascimento, cpf, endereco): + usuarios[cpf] = { + "nome": nome, + "data_nascimento": data_nascimento, + "endereco": endereco + } + + return "Usuario cadastrado com sucesso!" + + +def menu_cadastro_usuario(): + dados_usuario = {} + + while True: + opc = input(menu_confirma_usuario()).lower() + + 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.") + +def confirma_cpf_conta(): + while True: + cpf = input("Digite o cpf do usuario (apenas números): ").strip() + + 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 + + confirmacao = input(f"Confirma que o {usuarios[cpf]['nome']} é o titular?[s/n]: ").lower() + + if confirmacao == "s": + return cpf + else: + 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 + } + + +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']} + """) + + +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 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 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) 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 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() 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 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() 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) 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() 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 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" 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 000000000..c0b3f8ff6 Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v1/__pycache__/dominio.cpython-311.pyc differ 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 000000000..7c1a6a745 Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v1/__pycache__/servico.cpython-311.pyc differ 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 000000000..bbb3d62bf Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v2/__pycache__/bd.cpython-311.pyc differ diff --git a/07 - Banco de dados/desafio/desafio_v2/__pycache__/dominio.cpython-311.pyc b/07 - Banco de dados/desafio/desafio_v2/__pycache__/dominio.cpython-311.pyc new file mode 100644 index 000000000..43c1c85da Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v2/__pycache__/dominio.cpython-311.pyc differ 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 000000000..7b0377607 Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v2/__pycache__/servico.cpython-311.pyc differ diff --git a/07 - Banco de dados/desafio/desafio_v2/bd.py b/07 - Banco de dados/desafio/desafio_v2/bd.py new file mode 100644 index 000000000..42768acf3 --- /dev/null +++ b/07 - Banco de dados/desafio/desafio_v2/bd.py @@ -0,0 +1,38 @@ +import sqlite3 +from pathlib import Path +from sqlite3 import Connection, Cursor + + +def criar_bd(cursor: Cursor) -> 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 000000000..143a4de9c Binary files /dev/null and b/07 - Banco de dados/desafio/desafio_v2/db.sqlite differ 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 000000000..63d9f2ba9 Binary files /dev/null and b/07 - Banco de dados/meu_banco.sqlite differ 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 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 000000000..ad5bc1769 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/__init__.cpython-311.pyc differ 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 000000000..74db440c1 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/admin.cpython-311.pyc differ diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/apps.cpython-311.pyc b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/apps.cpython-311.pyc new file mode 100644 index 000000000..a7b957300 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/apps.cpython-311.pyc differ 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 000000000..87e67d00a Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/forms.cpython-311.pyc differ 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 000000000..f5122a5a6 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/models.cpython-311.pyc differ 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 000000000..c818f5a27 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/urls.cpython-311.pyc differ 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 000000000..9e9b77f8e Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/__pycache__/views.cpython-311.pyc differ 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 000000000..4f0e50c25 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/0001_initial.cpython-311.pyc differ 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 000000000..2039d9676 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/cards/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/12 - Desenvolvimento fullstack com Django/desafio/cards/models.py b/12 - Desenvolvimento fullstack com Django/desafio/cards/models.py new file mode 100644 index 000000000..6734d73b3 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/cards/models.py @@ -0,0 +1,34 @@ +from django.contrib.auth.models import User +from django.db import models + + +class Card(models.Model): + STATUS_CHOICES = ( + ("P", "Pendente"), + ("A", "Aprovado"), + ("E", "Enviado"), + ("R", "Recebido"), + ) + + CARD_NETWORK = ( + ("V", "Visa"), + ("M", "Mastercard"), + ) + + user = models.ForeignKey(User, on_delete=models.PROTECT, related_name="cards", verbose_name="Usuário") + name = models.CharField("Nome", max_length=20) + number = models.CharField("Número", max_length=16) + holder_name = models.CharField("Titular", max_length=20) + network = models.CharField("Rede", max_length=1, choices=CARD_NETWORK) + expiration_date = models.CharField("Data de expiração", max_length=5) + cvv = models.CharField("CVV", max_length=4) + status = models.CharField("Status", max_length=1, choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0]) + created_at = models.DateTimeField("Criado em", auto_now_add=True) + updated_at = models.DateTimeField("Alterado em", auto_now=True) + + def __str__(self) -> 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 000000000..ae2c3a1f9 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/__init__.cpython-311.pyc differ 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 000000000..4459f2352 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/settings.cpython-311.pyc differ 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 000000000..b88fa96a8 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/urls.cpython-311.pyc differ 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 000000000..7866b7a39 Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/asgi.py b/12 - Desenvolvimento fullstack com Django/desafio/config/asgi.py new file mode 100644 index 000000000..39149a0f8 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for config project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +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/desafio/config/settings.py b/12 - Desenvolvimento fullstack com Django/desafio/config/settings.py new file mode 100644 index 000000000..8ff972705 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/config/settings.py @@ -0,0 +1,89 @@ +import os +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = os.environ.get("SECRET_KEY", default="secret") + +DEBUG = True + +ALLOWED_HOSTS = [] + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "widget_tweaks", + "cards.apps.CardsConfig", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "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": [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" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +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", + }, +] + +LANGUAGE_CODE = "pt-br" + +TIME_ZONE = "America/Sao_Paulo" + +USE_I18N = True + +USE_TZ = True + +STATIC_URL = "static/" + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_REDIRECT_URL = "home" + +LOGOUT_REDIRECT_URL = "home" diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/urls.py b/12 - Desenvolvimento fullstack com Django/desafio/config/urls.py new file mode 100644 index 000000000..4daf352b3 --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/config/urls.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from django.contrib.auth import views as auth_views +from django.urls import include, path +from django.views.generic.base import TemplateView + +urlpatterns = [ + path("admin/", admin.site.urls), + path("login/", auth_views.LoginView.as_view(), name="login"), + path("logout/", auth_views.LogoutView.as_view(), name="logout"), + path("", TemplateView.as_view(template_name="home.html"), name="home"), + path("cards/", include("cards.urls", namespace="cards")), +] diff --git a/12 - Desenvolvimento fullstack com Django/desafio/config/wsgi.py b/12 - Desenvolvimento fullstack com Django/desafio/config/wsgi.py new file mode 100644 index 000000000..c0a96315d --- /dev/null +++ b/12 - Desenvolvimento fullstack com Django/desafio/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for config project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +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/desafio/db.sqlite3 b/12 - Desenvolvimento fullstack com Django/desafio/db.sqlite3 new file mode 100644 index 000000000..f892edb8c Binary files /dev/null and b/12 - Desenvolvimento fullstack com Django/desafio/db.sqlite3 differ 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 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