Olá rodimendes. Relativo a utilizaçã do Jupyter, o motivo foi mais porque eu queria testar algo que tinha aprendido rápido sem me preocupar com estrutura de pastas. O Notebook tem essa vantagem de você ficar executando apenas as células e isso já ajuda bastante.

Acho que comentei no artigo, mas se eu fosse usar esse código seria para uma API. Então respondendo a pergunta sobre estrutura que faria, eu organizaria meu código em algo assim:

src/
    __init__.py
	utils/
	  __init__.py
	  format_cnpj.py
	  hash.py
	controllers
	  __init__.py
	  register.py
	  authorization.py
	repositories
	  __init__.py
	  sqlite/
	     __init__.py
	     SqliteUserRepository.py
	     SqliteOrganizationRepository.py
	  UserRepository.py # interfaces do repositorio do usuário
	  OrganizationResitory.py # interfaces do repositório da organização
	errors/
	  __init__.py
	  UserNotFoundError.py
	  OrganizationNotFoundError.py
	services/
	  __init__.py
	  CreateUserService.py
	  CreateOrganizationService.py
	  AssociateUserWithOrganizationService.py
	  ChangeUserRoleService.py
	routes.py
setup_database.py	
app.py

Eu basicamente dividi o codigo em várias camadas e isso me permite realizar testes automatizados de forma mais isolada. Eu usaria mais alguns princípios do SOLID para isso. Contudo, não se preocupa muito com estrutura, pois isso acaba sendo pessoal e do momento de cada um.

Relativo a pergunta sobre a função hash, ela gera uma sequência de caracteres e sim, isso é variável para cada string. Tem como deixar ela um pouco mais fote usando outros parâmetros para deixar ela mais difícil de quebrar, como uma chave especial para concatenar com a senha ou informações do usuário concatenadas, para deixar o hash mais diferente ainda.

Por fim, obrigado por ter curtido o conteúdo. Eu estou postando mais coisas que estou vendo e quero colocar em prática.