Guia para Deploy Django Python 3

crianças fazendo deploy

Tutorial de Deploy por aí é o que não falta, a maioria em inglês. Esse que estou criando é pra engrossar o caldo de deploys em português. Esse é um Guia Definitivo Rápido, ou não tão rápido, para fazer Deploy Django com Python 3. É um deploy para Kids.

A dificuldade de fazer um deploy reside nos detalhes. No fundo é fácil se você está familiarizado com as partes envolvidas. Você precisa saber fazer uma autenticação ssh, estar acostumado com a linha de comando, conhecer linux, saber configurar o projeto, entender o que é servir arquivos estáticos, gunicorn…. tá, tá… nunca é fácil e muito menos rápido, justamente por isso criaram um monte de ferramentas pra deploy. E hoje com Ansible, Docker e whatever kids are using these days fica fácil fazer o deploy mas muito abstrato entender o funcionamento.

Em alguns anos esse post será obsoleto pra sempre, com serverless e tudo mais acho que pouca gente vai querer saber como fazer um deploy django dessa forma. Mas, mesmo assim, se ajudar uma pessoa já está bom. Será um tutorial Old-Style.

O Servidor

Vou imaginar que você não tem um servidor, não tem uma conta na AWS, nem DigitalOcean, nem Linode, nada… Você pode criar uma conta em um deles, lançar uma máquina com as configurações que quiser (Na AWS é tudo mais complicado pra quem não está acostumado, se for sua primeira vez, prefira outro).

Pra esse tutorial estou falando de Ubuntu 16.04, que é o servidor que você mais vai ver por aí nesse momento nesses serviços. Você pode escolher um Debian qualquer também.

Configuração inicial

Configure o timezone do servidor

sudo locale-gen --no-purge --lang pt_BR
sudo dpkg-reconfigure tzdata

Atualize os pacotes:

sudo apt-get update 
sudo apt-get -y upgrade

Instalando Python 3.6 no lugar do Python 3.5

Agora substitua o Python 3.5 instalado, pelo Python3.6 (O Ubuntu que indiquei, ele vem com Python 3.5.1)

sudo apt-get update
sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get install python3.6
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 1
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 2

Você pode escolher qual versão do Python o SO vai usar ao chamar python3 com:

sudo update-alternatives --config python3

Se você se enrolar, dê uma olhada aqui.

Instale os requisistos do Sistema Operacional

Aqui tem alguns pacotes que eu sempre uso em um deploy.

sudo apt-get install python3-pip nginx supervisor git git-core libpq-dev python-dev 
python-virtualenv

Seu projeto pode ter outros requirements do SO pra instalar.

VirtualEnvWrapper para o Python3

Eu gosto muito do VirtualEnvWrapper, acho simples de começar um virtualenv, e deixa todos os meus virtualenvs no mesmo lugar, mas isso é escolha pessoal, se você não gosta, faça como está acostumado

Você instala o virtualenvwrapper, depois define a pasta dos seus virtualenvs (WORKON_HOME).

Para usar com múltiplos Pythons você vai precisar definir VIRTUALENVWRAPPER_PYTHON. No caso estou usando sempre o padrão que defini para o comando python3. Isso não é um problema porque você pode criar um virtualenv depois apontando qual python aquele virtualenv vai usar.

sudo pip3 install virtualenvwrapper
echo 'export WORKON_HOME=~/Envs' >> ~/.bashrc
echo ‘export VIRTUALENVWRAPPER_PYTHON=`which python3`’ >> ~/.bashrc
echo 'source /usr/local/bin/virtualenvwrapper.sh' >> ~/.bashrc
source ~/.bashrc

Crie seu VirtualEnv apontando qual python aquele virtualenv irá usar

mkvirtualenv nome_venv --python=python3

O VirtualEnvWrapper é muito fácil, para entrar em um Virtualenv que você criou, você pode usar:

workon nome_venv

Para sair do virtualenv:

deactivate

Para excluir um virtualenv:

rmvirtualenv nome_venv

Gere as Chaves SSH para autenticar no GitHub

Você não quer (e nem deveria) escrever a senha pra fazer o git pull do seu projeto no servidor.

Gere as chaves SSH

cd ~/.ssh
ssh-keygen -t rsa -b 4096 -C "[email protected]"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa

Veja e copie o conteúdo da sua chave pública (id_rsa.pub)

cat ~/.ssh/id_rsa.pub

Depois entre no seu github, em Settings > SSH and GPG Keys. Clique em New SSH Key, dê um nome pra essa chave, como (“chaves do servidor teste”) e em Key cole o conteúdo da chave pública id_rsa.pub

Faça o clone do seu projeto Django

Copie o link SSH do Github para fazer o clone, no caso estou usando um projeto que encontrei agora pra exemplo

git clone [email protected]:kirpit/django-sample-app.git

Entre na pasta do projeto e instale os requirements do projeto.

Lembre-se de estar no virtual env correto.

cd django-sample-app/
pip install -r requirements.txt

Agora faça as alterações que forem necessárias para o deploy django, como criar um arquivo de settings_local, banco de dados, ou qualquer outra coisa específica do seu projeto.

Depois de tudo certo, você ainda precisa rodar as migrações e gerar os arquivos estáticos (se estiver usando)

python manage.py migrate
python manage.py collectstatic

Configurando o NGINX

Nginx, assim como o Apache, tem um mundo inteiro só deles, nesse momento você precisa conhecer o básico.

Existe um diretório /etc/nginx/sites-available/ onde ficão os arquivos de configuração de sites disponíveis para o nginx servir e existe o diretório /etc/nginx/sites-enabled/ que é a mesmíssima coisa, mas o que estiver aqui o Nginx estará servindo mesmo.

Normalmente você cria o arquivo de configuração no sites-available/ e cria um link simbólico para o sites-enabled/

Nós vamos fazer isso. Primeiramente, vou excluir o site default do nginx

sudo rm /etc/nginx/sites-enabled/default

Agora crie o arquivo de configuração para o seu site. (Se você não está acostumado com o VIM, use troque vi por nano)

sudo vi /etc/nginx/sites-available/meusite

No conteúdo do arquivo, coloque isto, mudando os caminhos necessários:

server {
 listen 80;
 access_log /home/usuario/logs/access.log;
 error_log /home/usuario/logs/error.log;

 server_name nome-site.com.br;

 location / {
 proxy_pass http://127.0.0.1:8000; 

 #As proximas linhas passam o IP real para o gunicorn nao achar que sao acessos locais
 proxy_pass_header Server;
 proxy_set_header X-Forwarded-Host $server_name;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;


 }

 location /static {

   alias /home/usuario/caminho_projeto/static/;

 }

}

Agora crie o link simbólico para o sites-enabled:

sudo ln -s /etc/nginx/sites-available/meusite /etc/nginx/sites-enabled/meusite

Reinicie o Nginx:

sudo service nginx restart


(Se você configurou tudo direitinho até aqui, ao acessar o site você verá uma página com um erro 502 Bad Gateway do próprio nginx)

Isso acontece porque ainda não tem nada aqui http://127.0.0.1:8000

Vamos colocar o site pra rodar nessa porta pelo gunicorn

Configurando o Gunicorn

Alguém vivo até essa parte? Cansa não, falta pouco.

No seu virtualenv (lembra workon nome_env?) instale o gunicorn

pip install gunicorn

Na pasta do seu projeto crie um arquivo chamado gunicorn_conf com:

bind = "127.0.0.1:8000"
logfile = "/home/usuario/logs/gunicorn.log"
workers = 3

Agora se você rodar o gunicorn, você vai ver seu site rodando:

/home/usuario/Envs/nome_venv/bin/gunicorn projeto.wsgi:application -c gunicorn_conf

Mas o que você pretende fazer? Rodar esse comando dentro de um screen e ir embora? Não dá né! Então, você vai usar o Supervisor pra controlar o funcionamento do gunicorn.

Configurando o Supervisor

Crie o seguinte arquivo de configuração

sudo vi /etc/supervisor/conf.d/gunicorn.conf

Com o seguinte conteúdo:

[program:gunicorn]
command=/home/usuario/Envs/nome_venv/bin/gunicorn projeto.wsgi:application -c /home/usuario/projeto/projeto_django/gunicorn_conf
directory=/home/usuario/projeto/projeto-django
user=usuario
autostart=true
autorestart=true
redirect_stderr=true

Depois é só avisar o supervisor que existe um novo processo que ele precisa controlar da seguinte forma:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

E voilá! Um site rodando você terá!

Conclusão

Existem muito mais coisas envolvidas no processo de um deploy. Você precisa configurar um firewall, provavelmente precisará servir mais pastas estáticas, etc, etc, etc… Mas precisa começar por algum lugar.

Não acredito que fiz um post inteiro sem colocar nenhum gif no meio, então só pra terminar, PRESTE ATENÇÃO em TODOS os caminhos que eu coloquei acima, você vai ter que usar os seus próprios paths corretamente

Oops..

Populando um campo novo não-nulo no Django

Fala pessoal, esse é um post rápido no qual eu vou mostrar como popular uma migração criando um campo novo não-nulo no django

SAY WUUUUT?????

george costanza taking his glasses off

Seguinte, sabe quando você tem seu projeto rodando, banco de dados, models, front, em produção as porra tudo e então aparece um requisito novo e eis que surge um campo obrigatório, que ninguém pensou antes, nem o cliente, nem o product owner, nem ninguém! Essa é a situação!

Acontece que você usa as migrações do django e você quer que poder colocar esses campos usando os migrations, tanto migrando pra frente quanto pra trás, ok?

Chega de conversa, pra esse exemplo resolvi pegar um projeto pronto nas interwebz, acabei optando por um django polls feito em django 1.10 já pronto.

Então lá se vão os passos de sempre, clonar e criar virtualenv…

git clone [email protected]:garmoncheg/django-polls_1.10.git
cd django-polls_1.10/
mkvirtualenv --python=/usr/bin/python3 django-polls 
pip install django==1.10  # Nesse projeto o autor não criou um requirements.txt
python manage.py migrate  # rodando as migrações existentes
python manage.py createsuperuser 
python manage.py runserver

Obs: Esse projeto pronto que pegamos está com uma migração faltando, então se você estiver seguindo esse passo a passo rode um python manage.py makemigrations pra que seja criada a migração 0002 (que é só a mudança de um verbose_name)

Agora você acessa o admin (http://localhost:8000/admin/polls/question/add/) e cria o seu poll :

 

django polls admin
Aí você pode ir lá no app polls e ver sua pergunta, responder etc…
até aí OK, ainda não fizemos nada!

Bom, o ideal é criar mais umas perguntas com datas de publicação diferentes para a brincadeira
ficar boa.

Depois de começar a usar você vai perceber que qualquer enquete fica infinita no seu site, ou seja, todas as vezes que alguém entra no site ele tem a oportunidade de votar, você nunca encerra a votação.

Então nossa alteração no código será a seguinte: De agora em diante, todas as enquetes terão uma data de expiração. No momento do cadastro o usuário poderá colocar a data na qual a votação se encerrará e o usuário será direcionado diretamente para o resultado. Queremos que essa data de expiração seja um campo obrigatório! E para os que já estão funcionando na plataforma vamos determinar arbitrariamente que cada um tenha um mês de validade a partir da data de publicação.

Antigamente, antes de migrações como isso era feito em qualquer sistema? Via SQL você criava um campo que permitisse NULOS, depois você criava uma query que populava esse campo e por fim você alterava a tabela pra tornar aquela coluna obrigatória. Com as migrações é a mesma coisa.

Então vamos criar o campo novo nos models vou chamá-lo de expires_date:

expires_date = models.DateTimeField(‘expires at’, null=True)

E o model inteiro fica assim:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at', null=True)

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'


Agora vamos criar a migração dessa mudança:

python manage.py makemigrations

E ele criará a migração 0003_question_expires_date. O conteúdo é o seguinte:

class Migration(migrations.Migration):

    dependencies = [
        ('polls', '0002_auto_20170429_2220'),
    ]

    operations = [
        migrations.AddField(
            model_name='question',
            name='expires_date',
            field=models.DateTimeField(null=True, verbose_name='expires at'),
        ),
    ]

 

Vamos modificar o código dessa migration, NO PANIC!

Populando o novo campo

Primeiro criamos uma função para popular o banco com as datas de expiração:

def populate_expires_date(apps, schema_editor):
    """
    Popula o campo data de expiração das perguntas já existentes colocando um mẽs de validade para cada.
    """
    from datetime import timedelta

    db_alias = schema_editor.connection.alias
    Question = apps.get_model('polls', 'Question')

    for row in Question.objects.using(db_alias).filter(expires_date__isnull=True):
        row.expires_date = row.pub_date + timedelta(days=30)
        row.save()

Originalmente usei esse código em um projeto que utiliza múltiplos bancos de dados, então precisei usar o db_alias e achei interessante deixá-lo aqui. Quanto ao Question, estou dando um import desse model usando o apps.get_model pois nesse momento que estamos rodando a migração o campo ainda não existe para o projeto, pois a migração não acabou de rodar, então é melhor que importar do model.

Agora, dentro da migração existe uma lista chamada operations. Nessa lista vamos adicionar os comandos para rodar a nossa função e em seguida, vamos adicionar a obrigatoriedade do campo: ficando dessa forma:

operations = [
    migrations.AddField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(null=True, verbose_name='expires at'),
    ),
    migrations.RunPython(populate_expires_date, reverse_code=migrations.RunPython.noop),
    migrations.AlterField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(verbose_name='expires at'),
    )
]

Você pode ver que utilizamos o migrations.RunPython para rodar nossa função durante a migração. O reverse_code serve quando alguém for dar um unapply da migração, nesse caso não existia o campo, então não faremos nada.

Logo em seguida adicionei a migração que altera o campo e ele deixa de ter null=True. Também poderíamos ter feito isso em outra migração, só retirando essa informação do model (que precisa ser retirada agora de qualquer forma) e ter criado uma nova migração.

O model ficará assim:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

agora você pode rodar o migrate tranquilamente:

python mange.py migrate

Pronto! Pra ver as alterações vou adicionar esse campo no admin, tanto para ser editado como no list_display:

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date', 'expires_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]
    list_display = ('question_text', 'pub_date', 'expires_date', 'was_published_recently')
    list_filter = ['pub_date']
    search_fields = ['question_text']

E voilá, todos as Questions que você tinha no polls contam com um expires_date, obrigatório e com 30 dias adicionado por default para as Questions antigas.

É isso aí, agora tem esse campo que queremos! O projeto com as alterações está aqui: https://github.com/ffreitasalves/django-polls_1.10

Se gostou, compartilhe com os amigos e deixe um comentário. Se não gostou também! Abraços!

 

Fontes:

http://stackoverflow.com/questions/28012340/django-1-7-makemigration-non-nullable-field

http://stackoverflow.com/questions/29217706/django-view-sql-query-without-publishinhg-migrations

https://realpython.com/blog/python/data-migrations/

https://docs.djangoproject.com/en/1.10/howto/writing-migrations/#non-atomic-migrations

O que aprendi gerenciando 3 produtos em 2016

Há exatamente um ano eu fiz um balanço sobre como foi o meu 2015 e como saí de uma fase de total esgotamento para a empolgação com uma empresa que estava nascendo. Atendendo a pedidos (Meu pai queria ler a continuação, huahuahua) resolvi contar de forma breve(?) como foi esse ano pra mim, pra minha carreira e o que aprendi gerenciando 3 produtos diferentes na mesma empresa.

Sobre os Desafios e Conquistas:

No ano passado(2015), consegui entregar o LegalNote. Começou pequeno, como deveria ser, afinal era só um MVP e precisávamos validar se existia mercado pra ele, se os advogados teriam interesse em um produto minimalista para controle de processos. Diversas vezes aplicamos os brilhantes métodos do Lean Startup do Eric Ries e quando se fala de Lean Startup necessariamente se fala do ciclo Build – Measure – Learn (tradução livre: Construir – Medir – Aprender)

Rapidamente aprendemos que nossos clientes precisavam que nós expandíssemos nossos robôs (doravante crawlers) para todos os tribunais do Brasil e todos os diários oficiais.

Como missão dada é missão cumprida, começamos o ano com a difícil tarefa de expandir nossos horizontes sobre fontes de informação pública. Se você já precisou algum dia buscar qualquer coisa que seja em um órgão público deve ter xingado o site algumas vezes. Pois é, o desafio de acessar sites do governo para estruturar e dar inteligência àquelas informações, tentar buscar padrões, trabalhar com dados nada estruturados é uma dor de cabeça. Se você quer que as coisas fiquem fáceis para o seu cliente, elas terão que ser difíceis pra você. Muitas partes em movimento, sites que podem mudar de uma hora pra outra e quebrar o seu crawler, mudança de padrões que podem quebrar seus RegEx, etc… Mas conseguimos. Nossa primeira fase de expansão sobre tribunais e diários oficiais foi um sucesso…

Mas com grandes poderes vem grandes responsabilidades. Ao longo desses anos trabalhando com startups me aprimorei em uma técnica pra atrair clientes com SEO. Usei pela primeira vez em 2013 e naquela época conseguimos alcançar clientes sem verba de marketing. Agora em 2016 usamos a mesma técnica e o resultado veio muito rápido. Muitos clientes e muitos processos.  Para vocês terem uma ideia, essa é a imagem que foi postada no Blog do LegalNote com os números da empresa em 2016:

O que aprendi gerenciando 3 produtos em 2016 - Fernando Alves

E pra todo número grande que você vê aí, tivemos um desafio de infra-estrutura. Desafios de escalabilidade, provisionamento de instâncias, controle e gerenciamento de filas, performance de banco de dados, monitoramento, performance de buscas, milhões de tasks, alta carga de emails e mais…

O bom é que todo problema traz consigo uma oportunidade pra aprender. Aprendi muito esse ano, muito mesmo, muito sobre performance, sobre escalabilidade, sobre AWS e principalmente sobre gerenciamento. Quando você é um chefe muito técnico existe uma grande possibilidade de você focar em desenvolver, afinal você é um cara mão-na-massa, mas na maioria das vezes isso não é o melhor pra você, nem para sua empresa. Você como gestor precisa o tempo todo facilitar para que as pessoas ao seu redor se desenvolvam. E o grupo sempre vai chegar mais longe que você, não importa quão bom você seja. Você precisa usar o seu conhecimento para alavancar a produtividade e o conhecimento das outras pessoas. Elas precisam melhorar o tempo todo e você também (Por isso fiquei emocionado ao ver a retrospectiva de final de ano de um cara brilhante que trabalha com a gente e ter lido a retrospectiva dele do ano passado)

Facilitar para que as pessoas se desenvolvam envolve:

  • Aumentar gradualmente o desafio que elas devem resolver
  • Estar disponível para conversar e tirar dúvidas
  • Se livrar dos impedimentos
  • Ouvir muito
  • etc

Só o que tenho aprendido como gestor dá mais alguns posts que prometo escrever esse ano.

Voltando ao ciclo, junto ao crescimento do LegalNote nós lançamos o Diligeiro. Primeiro a API ficou pronta, depois veio o lançamento do aplicativo Android, do WebApp e no segundo semestre lançamos o app para iOS

gerenciando-produtos-lançamento-diligeiro-ios
Almoço de lançamento do Diligeiro para iOS

Produto diferente, desafios diferentes. Logo aprendemos que os usuários queriam um chat in-app. Que foi a primeira feature a ser desenvolvida com o app já em funcionamento. Ficou tão legal, que outro dia mostrando para um pessoal no DevBeers tive que ouvir um “que é isso? é o Whatsapp?”

E foi através do Diligeiro que tive uma das minhas experiências mais gostosas como empreendedor. Há alguns meses precisei ir para Belo Horizonte participar de uma audiência. E conhecem aquela frase americana: Eat your own dog food, então a advogada que ia participar da audiência comigo na cidade foi contratada pelo aplicativo. Tudo correu bem, fomos à audiência e depois de sair do fórum a advogada me disse que estava praticamente só trabalhando como correspondente e utilizava o Diligeiro pra trabalhar. ( Explicando sucintamente: O Diligeiro é o Uber pra correspondentes jurídicos). Um mês depois recebi a decisão do Juiz através do LegalNote. Viagem completa dentro dos produtos da própria empresa, levantei comemorei, todo mundo bateu palma e a sensação que eu tive foi de uma vitória muito grande. E o diligeiro tem novidades por aí…

Caramba, ainda não parei de escrever…mas vamos lá. Dezenas de Milhares de pessoas físicas começaram a querer usar nossa ferramenta para advogados! Então porque não dar a essas pessoas o que elas querem?

gerenciando-produtos-shut up and take my money

Assim começamos a desenvolver um novo produto e lançamos agora no final do ano. É o SeuProcesso.com. Um court monitor para pessoa física.

Nossa corujinha

Ano que vem vocês ouvirão eu falar muito mais sobre o SeuProcesso.com

E como nem só de trabalho viverá o homem, pessoalmente esse ano foi incrível pra mim. Fiz o meu primeiro mergulho e foi uma das experiências mais extraordinárias que já tive. Viajei sozinho, fiz longas trilhas só ouvindo a natureza, fui em muitos parques, nadei, cantei, dormi, chorei. Li um livro que mudou minha vida (The Will Power Instinct) pq foi através dele que comecei a praticar meditação diariamente e o simples fato de sentar e respirar me deixa mais tranquilo, mais consciente de como estou me sentindo e olha que a vida inteira eu achava que isso era bullshit, só tentei dar uma chance ao HeadSpace pra começar a gostar e sentir diferença na minha vida. E o principal de tudo…foi o ano do Guanciale

Muito obrigado, 2017 tem mais!

Celery com múltiplas filas, retry e tarefas agendadas

No ano passado eu escrevi um post sobre Tarefas demoradas de forma assíncrona com Django e Celery, que serve para a maioria dos casos de uso do celery. O problema é que você pode querer e fazer muito mais com o celery, como ter filas diferentes para tarefas prioritárias, executar um retry após a falha de uma tarefa ou agendar a tarefa para ser executada apenas em outro horário ou outro dia.

Retry em uma Tarefa

Suponhamos que isso seja uma caveira que sua tarefa depende de uma API externa, conexão com algum site ou algum outro motivo que possa levá-la a um ConnectionError, por exemplo.

É provável que dentro de alguns instantes a API, site, etc. esteja no ar e que se você executar sua tarefa novamente ela funcione. Para isso podemos agendar um retry para esta tarefa:


from celery import shared_task
 
@shared_task(bind=True, max_retries=3)
def acessar_sistema_horrivel(self, meu_objeto_id):
    from core.models import MeuObjeto
    from requests import ConnectionError
 
    objeto = MeuObjeto.objects.get(pk=meu_objeto_id)
 
    # Se tiver ConnectionError tenta de novo em 180 segundos
    try:
 
        objeto.acessar_sistema_horrivel()
  
    except ConnectionError as exc:
        self.retry(exc=exc, countdown=180)
 
 

Interessante que dentro de uma função você está usando self.retry. Isso é possível graças ao bind=True no decorator do shared_task, que faz com que nossa função acessar_sistema_horrivel seja na verdade um método da classe Task. Isso nos obriga a colocar self como primeiro argumento da função também.

O número máximo de tentativas para executar essa tarefa é o max_retries=3 definido no decorator

ETA – Agendando a tarefa para começar mais tarde

Agora o cenário é o seguinte, seu sistema vai chamar uma tarefa assíncrona, mais quer que ela seja executada apenas daqui a uma hora.

Para isso podemos chamar a task setando a propriedade ETA (estimated time of arrival) e isso significa que sua tarefa só será executada depois daquele horário (não necessariamente para começar aquele horário, isso vai depender se você possui workers disponíveis, para agendar exatamente como no cron você pode usar o CeleryBeat) .

 


from django.utils import timezone

agora = timezone.now() 
 
# Depois é daqui a uma hora 
#(usei o replace pq quis mostrar, mas vc poderia usar o timedelta para somar também)
depois = agora.replace(hour=agora.hour + 1)
 
acessar_sistema_horrivel.apply_async((meu_objeto_id), eta=depois)
 

Colocando mais de uma fila de execução

Quando você executa o celery ele cria uma fila no seu broker (no caso do post anterior, era o RabbitMQ). A fila padrão chama celery. Qual a implicação? Se você possui várias tarefas assíncronas a serem executadas ficam todas na mesma fila, das qual os workers vão pegar as mensagens e executar.

Imagine que temos além da task acessar_sistema_horrivel, uma outra task chamada tarefa_demorada.

Então dado o seguinte cenário:

3 tarefas: uma lenta e uma mais rápida e a acessar_sistema_horrivel
1 fila
4 workers

E imagine que o sistema produziu 10 requisições da tarefa_demorada para serem executadas e em seguida produziu mais 10 da outra tarefa. O que vai acontecer? Seus workers ficarão todos ocupados buscando executando as requisições de tarefa_demorada que entraram primeiro e nenhum estará trabalhando na tarefa mais rápida.

A solução para isso é produzir filas diferenciadas para as tarefas prioritárias.


# CELERY ROUTES
CELERY_ROUTES = {
    'core.tasks.acessar_sistema_horrivel': {'queue': 'fila_horrivel'},
    'core.tasks.tarefa_demorada': {'queue': 'fila_demorada'},
    'core.tasks.tarefa_mais_rapida': {'queue': 'fila_rapida'},
}

Isso é suficiente para que as mensagens para essas tarefas sejam enviadas para essas filas do RabbitMQ. Detalhe, essas filas são criadas de forma permanente no RabbitMQ, mas isso também pode ser configurado.

Agora para rodar os consumidores para essas filas quando formos executar o celery podemos definir com o parâmetro -Q. No caso fica assim:


celery --app=nome_projeto worker -Q fila_horrivel,fila_demorada,fila_rapida --autoscale=10,5

Estamos iniciando os workers para todas essas filas. Neste caso estou colocando também o parâmetro autoscale, indicando para trabalhar com no mínimo 5 workers e caso haja muitas tarefas o celery adiciona mais workers até o máximo de 10. Isso é bom para economizar recursos do seu sistema, caso você tenha horários com mais produção de tarefas.

Como mostramos no outro post, para usar isto sob o supervisord, você pode modificar aquele mesmo script


[program:celery]
command=/home/deploy/.virtualenvs/meu_virtual_env/bin/celery --app=nome_projeto worker -Q fila_horrivel,fila_demorada,fila_rapida --autoscale=10,5
directory=/home/deploy/webapps/projeto_django
user=nobody
autostart=true
autorestart=true
redirect_stderr=true

Em seguida rodar o reread e o update, porque o arquivo de configuração mudou e reiniciar o supervisord para esse arquivo.


sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

Com isso seus workers vão executar tarefas de cada uma das filas, não travando os workers para só um tipo de tarefa. Há diversas outras formas de colocar seus workers para funcionar.

Chamando Tarefas Sequenciais

Outro problema comum, é precisar chamar duas tarefas assíncronas , uma depois da outra. Isso pode acontecer em diversos cenários, ex: o caso de a segunda tarefa receber como parâmetro o resultado da primeira, ou da primeira tarefa salvar algum dado e a segunda tarefa trabalhar aquele dado, etc…

Para isso nós podemos usar o Chain, que é o recomendado ao invés de chamar a segunda tarefa assíncrona com o delay dentro da primeira tarefa.


from celery import chain
from tasks import salvar_dados, trabalhar_dados
 
# Estamos chamando o método s das tasks, que as usa como subtasks de uma task gerada pelo chain.
chain(salvar_dados.s(meu_objeto_id) | trabalhar_dados.s())

A tarefa trabalhar_dados vai utilizar o return ou resultado da tarefa salvar_dados como parâmetro.
O chain em sí está gerando uma task, então para ele também serve chamar o apply_async com um eta definido, por exemplo:


chain(salvar_dados.s(meu_objeto_id) | trabalhar_dados.s()).apply_async(eta=depois)

Ignorando os resultados do ResultBackend

Se você só usa as tasks para executar alguma coisa que não precisa do return da task, você pode ignorar o ResultBackend e ter um ganho de performance.

A maioria dos casos não precisa guardar o resultado do return, então é só uma dica interessante se você já está salvando algo nos models, ou fazendo whatever diretamente dentro da sua task, coloque isso no seu settings.py:


CELERY_IGNORE_RESULT = True

 

Na verdade tem muito mais coisas legais no Celery, que ficarão pra um próximo post.

Fontes

http://docs.celeryproject.org/en/latest/userguide/tasks.html

http://docs.celeryproject.org/en/latest/userguide/optimizing.html#guide-optimizing

https://denibertovic.com/posts/celery-best-practices/

https://news.ycombinator.com/item?id=7909201

http://docs.celeryproject.org/en/latest/userguide/workers.html

http://docs.celeryproject.org/en/latest/userguide/canvas.html

 

Super Bônus

Celery Messaging at Scale at Instagram – Apresentação Pycon 2013

De esgotado a empolgado. O que deu certo aceitando um desafio em 2015

“Não será fácil. Mas você já fez coisas difíceis antes.” *

Depois de 10 semanas no vale do silício conversando com investidores, programadores e empreendedores de tecnologia, percebi que o produto que tinha me dedicado por mais de um ano não fazia mais sentido pra mim.

eeeee. nope!

 

Comecei 2015 exausto. Depois de vários anos empreendendo estava me perguntando se era hora de prestar um concurso público ou trabalhar “na minha”.

Pensei em desistir de empreender, pensei em como é difícil tirar algo do papel e sabia que se quisesse empreender teria que passar por várias etapas pelas quais já tinha passado algumas vezes nos últimos anos. Mas as vezes não adianta querer fugir, e o que foi uma simples conversa informal que tive com o Dr Antônio Maia no EC14 acabou se transformando num convite para ser sócio de uma empresa com ideias bastante ambiciosas para transformar a área jurídica no Brasil. As ideias eram excitantes e boas demais para serem preteridas e resolvi então abraçar mais um desafio como CTO de uma empresa de Legal Tech.

Studying law. making a responsible choice for my future

Quem já foi CTO no início de uma startup sabe como é difícil você dar os primeiros passos. Estudar as necessidades de um mercado que não é o seu, planejar as estruturas, começar a escrever o código e o principal e mais difícil: Montar um time de excelência.

Não tem todo mundo, mas tem bastante gente

Não tem todo mundo, mas tem bastante gente

Existe um provérbio grego que diz que O início é metade de qualquer ação. Acho essa metáfora linda, pois não é pra tanto, mas mesmo assim começamos do zero na última semana de fevereiro/15 com uma única posição dentro de um escritório de advocacia e estamos terminando o ano com um conjunto inteiro na Av. Paulista, um time lindo de desenvolvimento, um stande e  apresentação na maior feira da nossa área, com um produto lançado, o LegalNote, crescendo rapidamente com mais de mil usuários cadastrados e milhares de processos e com o nosso outro produto o Diligeiro correndo na fase final de desenvolvimento.

Trabalhamos até a véspera da véspera de ano novo, empolgados por todas essas conquistas que não foram fáceis e eu tenho a certeza de que daqui pra frente tudo continuará sendo difícil, mas nós podemos olhar pra trás e ver que já fizemos coisas difíceis antes.

Muito obrigado à todos! Um excelente 2016!

Um obrigado especial a todos que trabalham duro comigo:

Chuckeeey
Daniel , o mago das regex
Derek Oedenkoven
Dr Antonio Maia
Dr Fabio Abrahao
Gui AMS
Marcus Beckenkamp
Vitão Jar Jar
Gustavo UX
Rafael, o lendário!
Muller #Zueiro
* Li essa frase em algum lugar essa semana e achei fantástica.

E quando uma empresa gigante resolve virar sua concorrente?

Você cria um produto, vira pioneiro em uma área, corre todos os riscos e fica cheio de incertezas, tanto suas, quanto dos clientes confrontados com “A novidade.”

Imagine que nesse cenário, um pequeno grupo abraça aquilo que você fez, ama o seu projeto e você conseguiu criar algo dentro de uma comunidade.

De repente, o gigante acordou, imagine um gigante mesmo, a própria Apple. E ela aprova sua ideia. Aprova tanto que resolve criar uma versão própria do seu produto.

Agora a Apple é sua concorrente!

Steve Jobs - Doctor Evil

E aí, senta e chora?


Isso aqui não é uma anedota, é um caso verídico.

Os criadores do smartwatch Pebble já passaram por vários perrengues, foram acelerados pela YCombinator, o projeto não foi pra frente, não conseguiram levantar capital porque eram uma empresa de Hardware, depois concluíram com sucesso uma campanha no kickstarter e por fim foram lançando e evoluindo o Pebble, tudo a partir da ideia do Eric Migicovsky de criar um display de relógio que mostrasse as notificações do celular.

Bom, imagine a cabeça desse cara quando o Google resolveu lançar o próprio smartwatch e quando a Apple resolveu lançar o Apple Watch.

Agora imagina como ele se sente, vendo que um concorrente desse tamanho, um gigante, está mais abrindo mercado para eles do que prejudicando.

E pode ser que um dia o Pebble se torne tão grande e vire realmente um competidor do Apple Watch. Quem sabe…

 

Por quê estou falando tudo isso?

Porque com o lançamento do Apple Watch muita gente que não tinha ideia do que era um SmartWatch acabou descobrindo o que é isso e, independente de descobrir através da Apple, puderam também se interessar e ir atrás de outros modelos, conhecendo o Pebble, que Dobrou suas vendas!

Veja: Aparentemente Apple Watch ajudou a DOBRAR as vendas do Pebble

 

Me inspirei pra escrever no post do Diogo Novaes no facebook. Aliás, um cara que vale muito a pena seguir.

Tarefas demoradas de forma assíncrona com Django e Celery

Estou escrevendo esse post empolgado por uma lightning talk que fiz no GruPy-SP (16/07). Assim aproveito pra explicar um pouco mais o que estou dizendo nos slides.

O problema de tarefas demoradas do lado do servidor

Sempre que o cliente faz uma requisição web (request), o servidor faz um processamento, normalmente pra quem usa o django, ele lê a requisição, trata os dados recebidos, salva ou recupera registros do banco de dados (através dos models), faz algum processamento do que será exibido para o usuário, renderiza isso em um template e manda uma resposta (response) para o cliente.

Dependendo da tarefa que você executa no servidor a resposta pode demorar muito e isso leva à problemas de TimeOut, a experiência do usuário fica comprometida (quanto mais o servidor demorar pra responder maior a chance do usuário sair do seu site) e também pode acontecer da lentidão ser causada por outros, como por exemplo o uso de uma API externa.

Existem diversas tarefas que podem demorar pra ser executadas. Se você tiver que criar um relatório pesado, acionado por um client web. Se de repente você precisar enviar diferentes emails para uma lista ou por exemplo editar um vídeo depois que o usuário faz o upload na sua página.

Caso real

Esse é um problema que me deparei um dia na geração de um relatório. O relatório levava cerca de 20 minutos para ser gerado, o cliente recebia um erro de timeout e obviamente não dá pra esperar 20 minutos pra gerar alguma coisa. Pra driblar isso e deixar a tarefa em background, alguém resolveu usar um comando do sistema operacional. (No linux se você colocar & no final de um comando esse comando roda em background).

django-view-os-system

Note que em seguida o django envia mostra uma mensagem ao usuário de que ele será avisado por e-mail ao final da geração do relatório.
Alguma coisa não parece certa aqui.

Me arrependo imediatamente dessa decisão

Celery – A solução para esses problemas!

O Celery é um gerenciador de tarefas assíncronas. Com ele você pode executar uma fila de tarefas (que ele recebe por meio de mensagens), pode agendar tarefas direto no seu projeto sem precisar do cron e ele ainda tem integração fácil com a maioria dos frameworks python mais utilizados (django, flask, tornado, etc.)

Como o Celery funciona

Essa imagem kibada da internet dá uma visão geral de como fica o fluxo da sua aplicação.

estrutura-celery

  • O User (ou Client ou Producer) é a sua aplicação Django.
  • O AMPQ Broker é um Message Broker. Um programa responsável por manter a fila de mensagens que serão trocadas entre o seu programa e o Celery, geralmente é o RabbitMQ ou o Redis
  • Os workers (ou consumers) que vão executar as tarefas que você quer que sejam assíncronas.
  • O Result Store, que é onde os workers podem ou não salvar os resultados das tarefas que foram executadas.

O Client pode passar uma tarefa, uma lista de tarefas, tarefas periódicas ou o retry de alguma tarefa pra fila do Message Broker. O Message Broker distribui essas tarefas entre os workers e o resultado dessas tarefas pode ser escrito em um Result Store (Memcahed, RDBMS, MongoDB, etc…) que mais tarde pode ser lido pelo Client novamente.

Qual Broker utilizar?

O recomendado pelo Celery é o RabbitMQ.

Instalando e configurando o RabbitMQ

Existem vários exemplos de como utilizar o Redis, eu vou mostrar como usar o RabbitMQ.

Você só precisa:

  1. Instalar o RabbitMQ (Baixar o pacote no site ou adicionar o repo deles no sources.list e instalar com APT-GET)
    sudo apt-get install rabbitmq-server
  2. Criar um usuário, um virtual host e dar permissões para esse usuário no virtualhost:
    
    sudo rabbitmqctl add_user myuser mypassword
    sudo rabbitmqctl add_vhost myvhost
    sudo rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"
    

Instalando e configurando o Celery

pip install celery

No seu settings.py:

#Celery Config
BROKER_URL = 'amqp://guest:[email protected]:5672//'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

Na pasta do projeto (a mesma pasta do settings.py) crie um arquivo chamado celery.py:


from __future__ import absolute_import

import os
from celery import Celery
from django.conf import settings

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nome_do_proj.settings')

app = Celery('nome_do_proj')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Esse autodiscover_tasks permite que seu projeto ache todas as tarefas assíncronas de cada app instalada nele. Nessa mesma pasta do settings.py, você vai modificar o arquivo de pacote __init__.py:


from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

Criando as tarefas assíncronas do seu app

Na pasta do seu app crie um arquivo chamado tasks.py:

from __future__ import absolute_import
from celery import shared_task
from relatorios import gerar_relatorio_excel


@shared_task
def gerar_relatorio(data_inicial, data_final, email):
    gerar_relatorio_excel(
        data_inicial = data_inicial,
        data_final = data_final,
        email = email
    )
    return "Relatorio Excel gerado"

Agora é só fazer importar essa função que foi criada usando o método delay que foi adicionado à sua função pelo decorator shared_task.

tabelao assincrono

Rodando o Celery

Agora você precisa rodar o celery. Ele vai rodar os seus workers pra executar suas tarefas de forma assíncrona. Por padrão ele inicia um worker para cada núcleo de processador, mas você pode determinar quantos com -c Numero_de_workers

celery --app=nome_projeto worker --loglevel=INFO

E então você verá essa tela:

celery-rodando

Você pode então testar sua função assíncrona com o shell do django mesmo:

chama funcao

E o que aparece na tela que está mostrando o celery:

aparece no celery

Colocando o Celery em Produção

Para usar o Celery em produção eu recomendo que você use o Supervisor

Para instalar o supervisor:

sudo apt-get install supervisor

Depois crie um arquivo de configuração para o celery e coloque um link simbólico para esse arquivo no /etc/supervisor/conf.d/

[program:celery]
command=/home/deploy/.virtualenvs/meu_virtual_env/bin/celery --app=nome_proj worker --loglevel=INFO
directory=/home/deploy/webapps/projeto_django
user=nobody
autostart=true
autorestart=true
redirect_stderr=true

Para o supervisor ler novamente os arquivos que estão em sua pasta de configuração:

sudo supervisorctl reread
sudo supervisorctl update

Agora é só iniciar o celery:

sudo supervisorctl start celery

Bom, acho que é só isso. Esses são os slides da minha apresentação:

Continuação

Celery com múltiplas filas, retry e tarefas agendadas

Fontes

E aqui vão as fontes de pesquisa (ou algumas) que eu utilizei nesse processo:

Bônus:

Como o Feed do Instagram usa o Celery e o RabbitMQ

Um pequeno prazer de uma startup que não deu certo.

 

Quem nunca teve uma empresa que não deu certo, que atire a primeira pedra.

Sempre gostei de empreendedorismo. Eu gostava de ler matérias sobre empresas, gostava de ir nas empresas dos meus pais e gostava dos filmes dos anos 80 estilo Jerry Maguire. Minha família sempre foi empreendedora. Meu pai teve de loja de material de aquarismo até distribuidora de salgadinho de bar, meu tio construiu a maior rede de salões de beleza da zona norte e até hoje não se passa uma semana sem que minha mãe me fale uma ideia nova que ela teve (Se eu publicar alguma, ela me mata). O fato é que o empreendedorismo está no meu sangue.

No final de 2011, eu e meu amigo Daniel resolvemos montar uma startup. Na ocasião eu tinha um site que revendia instrumentos musicais. Nada muito formal, mas era uma escola pra mim. Estava ganhando dinheiro, me mantendo, tive a oportunidade de largar um emprego público e fazer mais dinheiro em casa de cueca do que indo perfumado até o prédio da FEA. Falar sobre a Apoio Musical levaria até mais de um post sobre os 2 anos que eu a mantive no ar e ela me manteve.

Bom, montamos essa startup porque estávamos vidrados em Crowdsourcing. Queríamos muito, que o crowdsourcing fosse um jeito de dar às empresas a oportunidade de gastar pouco pra ter ideias de qualidade e de dar às pessoas a oportunidade de trabalhar em projetos de grandes empresa, mostrando seu potencial.

 

O primeiro problema que enfrentaríamos era o do ovo e o da galinha: como teríamos empresas sem ter pessoas interessadas em anunciar e como teríamos pessoas pra participar dos desafios sem ter empresas com desafios?

Enfim, resolvemos começar buscando às pessoas. (O que hoje eu acredito que não foi a melhor estratégia).

Pra alcançar essas pessoas nós resolvemos criar um Desafio com Ideias que pudessem melhorar a cidade de São Paulo. Chamamos isso de Desafio São Paulo.

Criamos um aplicativo para o facebook pra poder receber as ideias, já que ainda não tínhamos uma plataforma criada e não me lembro como alguém gostou do desafio e ele foi parar no Catraca Livre.

ideias-na-mesa-catraca-livre

Isso foi o suficiente pra várias pessoas entrarem no Desafio. Nisso tivemos um outro problema: Como escolher qual ideia é a melhor? E nisso, a CAOS Focado que é uma empresa de consultoria nos ajudou criando um método objetivo pra definir quais eram as melhores ideias (eu deveria ter filmado pra mostrar quão genial foi o Miguel Chaves resolvendo isso pra gente).

A startup como você já previu no começo do texto, escafedeu-se, fizemos o desafio, não conseguimos fechar com nenhuma outra empresa um desafio sequer, apesar de negociar durante meses com uma empresa grande da área de turismo. Então o que eu estou comemorando aqui como um pequeno prazer? Olhe as ideias escolhidas como as melhores para São Paulo no nosso desafio e pense no que mudou em São Paulo de 2012 para 2015.

  • Ciclo Faixas com acesso a CPTM nas marginais
  • Menos vagas de rua para carros
  • Ruas de lazer aos domingos
  • Transporte Coletivo 24 horas
  • WiFi gratuito em locais públicos

Isso tudo começou a mudar em São Paulo e vai continuar mudando porque agora já é tendência. Se quiser ter certeza, confere meu post original de quando eu publiquei o desafio em março de 2012:

 

http://blog.ideiasnamesa.com.br/desfile-das-campeas-desafio-sao-paulo/

*args e **kwargs não são coisas mágicas em Python

* é um operador e não é só de multiplicação. Hun!?

Muita gente acha que *args e **kwargs são coisas mágicas em Python.

* é um operador que transforma listas em argumentos de uma função.

Por exemplo, imagina que você quer usar a função replace em uma string. Essa função recebe 2 argumentos, o primeiro para o valor que precisa ser substituído e o segundo para o novo valor.

>>> 'a escambau'.replace('a ', 'o ')
>>> 'o escambau'
>>> # Agora tentando passar os parâmetros com uma lista
>>>
>>> args = ['a ','o ']
>>> 'a escambau'.replace(args)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: replace() takes at least 2 arguments (1 given)
>>> 
>>> #Para que isso funcione basta usar o operador *
>>> 'a escambau'.replace(*args)
'o escambau'


Super simples.

O ** segue a mesma lógica, é um operador que transforma um dicionário em keyword arguments de uma função.

Vou criar uma função que recebe dois argumentos e printa o nome com a idade (como na revista caras)

>>> d = {'nome' : 'Fernando', 'idade': 29}
>>> def caras_me(nome,idade):
...     print("%s(%s)" % (nome,idade))
... 
>>> #Colocando só o dicionário sem o operador:
>>> caras_me(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: caras_me() takes exactly 2 arguments (1 given)
>>> # Colocando o dicionário usando o operador **
>>> caras_me(**d)
Fernando(29)

Você pode dar uma olhada na documentação oficial, caso queira:

https://docs.python.org/2/tutorial/controlflow.html#unpacking-argument-lists

Usando Regex para fazer split para mais de um valor

Precisei fazer split em alguns textos jurídicos e como na língua portuguesa os adjetivos sofrem flexão de gênero , tive que separar um texto por mais de um valor.

A sorte é que o módulo re de regex do python é excelente e vem com essa função split, você só precisa passar o pattern e o texto. No caso eu usei ‘Advogado|Advogada ‘, lembrando que o |(pipe) é um “ou” em expressões regulares.