Planeta PythonBrasil

PythonBrasil[10]

April 26, 2017

PythonClub

What the Flask? pt 4 - Extensões para o Flask

What The Flask - 4/5

Finalmente!!! Depois de uma longa espera o What The Flask está de volta! A idéia era publicar primeiro a parte 4 (sobre Blueprints) e só depois a 5 sobre como criar extensões. Mas esses 2 temas estão muito interligados então neste artigo os 2 assuntos serão abordados. E a parte 5 será a final falando sobre deploy!

code
  1. Hello Flask: Introdução ao desenvolvimento web com Flask
  2. Flask patterns: Estruturando aplicações Flask
  3. Plug & Use: extensões essenciais para iniciar seu projeto
  4. Magic(app): Criando Extensões para o Flask(<-- Você está aqui)
  5. Run Flask Run: "deploiando" seu app nos principais web servers e na nuvem

Não sei se você ainda se lembra? mas estavámos desenvolvendo um CMS de notícias, utilizamos as extensões Flask-MongoEngine, Flask-Security, Flask-Admin e Flask-Bootstrap.

E neste artigo iremos adicionar mais uma extensão em nosso CMS, mas iremos criar uma extensão ao invés de usar uma das extensões disponíveis.

Extensão ou Plugin? Por definição plugins diferem de extensões. Plugins geralmente são externos e utilizam algum tipo de API pública para se integrar com o aplicativo. Extensões, por outro lado, geralmente são integradas com a lógica da aplicação, isto é, as interfaces do próprio framework. Ambos, plugins e extensões, aumentam a utilidade da aplicação original, mas plugin é algo relacionado apenas a camadas de mais alto nível da aplicação, enquanto extensões estão acopladas ao framework. Em outras palavras, plugin é algo que você escreve pensando apenas na sua aplicação e está altamente acoplado a ela enquanto extensão é algo que pode ser usado por qualquer aplicação escrita no mesmo framework pois está acoplado a lógica do framework e não das aplicações escritas com ele.

Quando criar uma extensão?

Faz sentido criar uma extensão quando você identifica uma functionalidade que pode ser reaproveitada por outras aplicações Flask, assim você mesmo se beneficia do fato de não precisar reescrever (copy-paste) aquela funcionalidade em outros apps e também pode publicar sua extensão como open-source beneficiando toda a comunidade e incorporando as melhorias, ou seja, todo mundo ganha!

Exemplo prático

Imagine que você está publicando seu site mas gostaria de prover um sitemap. (URL que lista todas as páginas existentes no seu site usada pelo Google para melhorar a sua classificação nas buscas).

Como veremos no exemplo abaixo publicar um sitemap é uma tarefa bastante simples, mas é uma coisa que você precisará fazer em todos os sites que desenvolver e que pode se tornar uma funcionalidade mais complexa na medida que necessitar controlar datas de publicação e extração de URLs automáticamente.

Exemplo 1 - Publicando o sitemap sem o uso de extensões

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/artigos')
def artigos():
    "este endpoint retorna a lista de artigos"

@app.route('/paginas')
def paginas():
    "este endpoint retorna a lista de paginas"

@app.route('/contato')
def contato():
    "este endpoint retorna o form de contato"

######################################
# Esta parte poderia ser uma extensão
######################################
@app.route('/sitemap.xml')
def sitemap():
    items = [
        '<url><loc>{0}</loc></url>'.format(page)
        for page in ['/artigos', '/paginas', '/contato']
    ]
    sitemap_xml = (
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">{0}</urlset>'
    ).format(''.join(items)).strip()
    response = make_response(sitemap_xml)
    response.headers['Content-Type'] = 'application/xml'
    return response
#######################################
# / Esta parte poderia ser uma extensão
#######################################


app.run(debug=True)

Executando e acessando http://localhost:5000/sitemap.xml o resultado será:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url><loc>/artigos</loc></url>
    <url><loc>/paginas</loc></url>
    <url><loc>/contato</loc></url>
</urlset>

NOTE: O app acima é apenas um simples exemplo de como gerar um sitemap e a intenção dele aqui é apenas a de servir de exemplo para extensão que criaremos nos próximos passos, existem outras boas práticas a serem seguidas na publicação de sitemap mas não é o foco deste tutorial.

Vamos então transformar o exemplo acima em uma Extensão e utilizar uma abordagem mais dinâmica para coletar as URLs, mas antes vamos entender como funcionam as extensões no Flask.

Como funciona uma Extensão do Flask?

Lembra que na parte 2 desta série falamos sobre os patterns do Flask e sobre o application factory e blueprints? As extensões irão seguir estes mesmos padrões em sua arquitetura.

O grande "segredo" para se trabalhar com Flask é entender que sempre iremos interagir com uma instância geralmente chamada de app e que pode ser acessada também através do proxy current_app e que sempre aplicaremos um padrão que é quase funcional neste deste objeto sendo que a grande diferença aqui é que neste paradigma do Flask as funções (chamadas de factories) introduzem side effects, ou seja, elas alteram ou injetam funcionalidades no app que é manipulado até que chega ao seu estado de execução. (enquanto em um paradigma funcional as funções não podem ter side effects)

Também é importante entender os estados configuração, request e interativo/execução do Flask, asunto que abordamos na parte 1 desta série.

Em resumo, iremos criar uma factory que recebe uma instância da classe Flask, o objeto app (ou o acessa através do proxy current_app) e então altera ou injeta funcionalidades neste objeto.

Dominar o Flask depende muito do entendimento desse padrão de factories e os 3 estados da app citados acima, se você não está seguro quanto a estes conceitos aconselho reler as partes 1 e 2 desta série (e é claro sinta se livre para deixar comentários com as suas dúvidas).

Só para relembrar veja o seguinte exemplo:

app = Flask(__name__)  # criamos a instancia de app
admin = Admin()  # instancia do Flask-Admin ainda não inicializada

do_something(app)  # injeta ou altera funcionalidades do app
do_another_thing(app, admin)  # injeta ou altera funcionalidades do apo ou do admin
Security(app)  # adiciona funcionalidades de login e controle de acesso
Cache(app)  # adiciona cache para views e templates
SimpleSitemap(app)  # A extensão que iremos criar! Ela adiciona o /sitemap.xml no app

admin.init_app(app)  # agora sim inicializamos o flask-admin no modo lazy

Olhando o código acima pode parecer bastante simples, você pode achar que basta receber a intância de app e sair alterando sem seguir nenhum padrão.

def bad_example_of_flask_extension(app):
    "Mal exemplo de factory que injeta ou altera funcionalidades do app"
    # adiciona rotas
    @app.route('/qualquercoisa)
    def qualquercoisa():
        ...
    # substitui objetos usando composição
    app.config_class = MyCustomConfigclass
    # altera config
    app.config['QUALQUERCOISA'] = 'QUALQUERVALOR'
    # sobrescreve métodos e atributos do app
    app.make_responde = another_function
    # Qualquer coisa que o Python (e a sua consciência) permita!

Isso pode provavelmente funcionar mas não é uma boa prática, existem muitos problemas com o factory acima e alguns deles são:

  1. Nunca devemos definir rotas dessa maneira com app.route em uma extensão o correto é usar blueprints.
  2. Lembre-se dos 3 estados do Flask, devemos levar em consideração que no momento que a aplicação for iniciada a extensão pode ainda não estar pronta para ser carregada, por exemplo, a sua extensão pode depender de um banco de dados que ainda não foi inicializado, portanto as extensões precisam sempre ter um modo lazy.
  3. Usar funções pode se ruma boa idéia na maioria dos casos, mas lembre-se que precisamos manter estado em algumas situações então pode ser melhor usar classes ao invés de funções pois as classes permitem uma construção mais dinâmica.
  4. Nossa extensão precisa ser reutilizavel em qualquer app flask, portanto devemos usar namespaces ao ler configurações e definir rotas.

NOTE: Eu NÃO estou dizendo que você não deve usar funções para extender seu app Flask, eu mesmo faço isso em muitos casos. Apenas tenha em mente esses detalhes citados na hora de decidir qual abordagem usar.

Patterns of a Flask extension

Preferencialmente uma Extensão do Flask deve seguir esses padrões:

  • Estar em um módulo nomeado com o prefixo flask_ como por exemplo flask_admin e flask_login e neste artigo criaremos o flask_simple_sitemap. (NOTE: Antigamente as extensões usavam o padrão flask.ext.nome_extensao mas este tipo de plublicação de módulo com namespace do flask.ext foi descontinuado e não é mais recomendado.)
  • Fornecer um método de inicialização lazy nomeado init_app.
  • Ler todas as suas configurações a partir do app.config
  • Ter suas configurações prefixadas com o nome da extensão, exemplo: SIMPLE_SITEMAP_URLS ao invés de apenas SITEMAP_URLS pois isto evita conflitos com configurações de outras extensões.
  • Caso a extensão adicione views e URL rules, isto deve ser feito com Blueprint
  • Caso a extensão adicione arquivos estáticos ou de template isto também deve ser feito com Blueprint
  • ao registrar urls e endpoints permitir que sejam dinâmicos através de config e sempre prefixar com o nome da extensão. Exemplo: url_for('simple_sitemap.sitemap') é melhor do que url_for('sitemap') para evitar conflitos com outras extensões.

NOTE: Tenha em mente que regras foram feitas para serem quebradas, O Guido escreveu na PEP8 "A Foolish Consistency is the Hobgoblin of Little Minds", ou seja, tenha os padrões como guia mas nunca deixe que eles atrapalhem o seu objetivo. Eu mesmo já quebrei essa regra 1 no flasgger, eu poderia ter chamado de flask_openapi ou flask_swaggerui mas achei Flasgger um nome mais divertido :)

De todos os padrões acima o mais importante é o de evitar o conflito com outras extensões!

Zen do Python: Namespaces são uma ótima idéia! vamos usar mais deles!

Criando a extensão Simple Sitemap

Ok, agora que você já sabe a teoria vamos colocar em prática, abre ai o vim, emacs, pycharm ou seu vscode e vamos reescrever o nosso app do Exemplo 1 usando uma extensão chamada flask_simple_sitemap e para isso vamos começar criando a extensão:

A extensão será um novo módulo Python que vai ser instalado usando setup ou pip portanto crie um projeto separado.

Em seu terminal *nix execute os comandos:

➤ $
# Entre na pasta onde vc armazena seus projetos
cd Projects

# crie o diretório root do projeto
mkdir simple_sitemap
cd simple_sitemap

# torna a extensão instalável (vamos escrever esse arquivo depois)
touch setup.py

# é sempre bom incluir uma documentação básica
echo '# Prometo documentar essa extensão!' > README.md

# se não tiver testes não serve para nada! :)
touch tests.py

# crie o diretório que será o nosso package
mkdir flask_simple_sitemap

# __init__.py para transformar o diretório em Python package
echo 'from .base import SimpleSitemap' > flask_simple_sitemap/__init__.py

# A implementação da extensão será escrita neste arquivo
# (evite usar main.py pois este nome é reservado para .zipped packages)
touch flask_simple_sitemap/base.py

# Crie a pasta de templates
mkdir flask_simple_sitemap/templates

# usaremos Jinja para gerar o XML
touch flask_simple_sitemap/templates/sitemap.xml

# incluindo templates no build manifest do setuptools
echo 'recursive-include flask_simple_sitemap/templates *' > MANIFEST.in

# precisaremos de um arquivo de requirements para os testes
touch requirements-test.txt

Agora voce terá a seguinte estrutura:

➤ tree
simple_sitemap/
├── flask_simple_sitemap/
│   ├── base.py
│   ├── __init__.py
│   └── templates/
│       └── sitemap.xml
├── MANIFEST.in
├── README.md
├── requirements-test.txt
├── setup.py
└── tests.py

2 directories, 8 files

O primeiro passo é escrever o setup.py já que a extensão precisa ser instalavél:

from setuptools import setup, find_packages

setup(
    name='flask_simple_sitemap',
    version='0.0.1',
    packages=find_packages(),
    include_package_data=True,
    zip_safe=False
)

Eu tenho o costumo de praticar o que eu chamo de RDD (Readme Driven Ddevelopment), ou seja, ao criar projetos como este eu costumo escreve primeiro o README.md explicando como deve funcionar e só depois de ter isto pronto que eu começo a programar.

Edite o README.md

# Flask Simple Sitemap

Esta extensão adiciona a funcionalidade de geração de sitemap ao seu app flask.

## Como instalar?

Para instalar basta clonar o repositório e executar:

    $ python setup.py install

Ou via `pip install flask_simple_sitemap`

## Como usar?

Basta importar e inicializar:

    from flask import Flask
    from flask_simple_sitemap import SimpleSitemap

    app = Flask(__name__)
    SimpleSitemap(app)

    @app.route('/)
    def index():
        return 'Hello World'

Como em toda extensão Flask também é possível inicializar no modo Lazy chamando
o método `init_app`

## Opções de configuração:

esta extensão utiliza o namespace de configuração `SIMPLE_SITEMAP_`

- **SIMPLE_SITEMAP_BLUEPRINT** define o nome do blueprint e do url prefix (default: `'simple_sitemap'`)
- **SIMPLE_SITEMAP_URL** define a url que irá renderizar o sitemap (default: `'/sitemap.xml'`)
- **SIMPLE_SITEMAP_PATHS** dicionário de URLs a serem adicionadas ao sitemap (exemplo: URLs criadas a partir de posts em bancos de dados)

Agora que já sabemos pelo README o que queremos entregar de funcionalidade já é possível escrever o tests.py e aplicar também um pouco de TDD

O Flask tem uma integração bem interesante com o py.test e podemos editar o tests.py da seguinte maneira:

NOTE: O ideal é fazer o test setup no arquivo conftest.py e usar fixtures do py.test, mas aqui vamos escrever tudo junto no tests.py para ficar mais prático.

Zen do Python: praticidade vence a pureza :)

####################################################################
# Início do Test Setup
#

import xmltodict
from flask import Flask
from flask_simple_sitemap import SimpleSitemap

app = Flask(__name__)
extension = SimpleSitemap()

app.config['SIMPLE_SITEMAP_BLUEPRINT'] = 'test_sitemap'
app.config['SIMPLE_SITEMAP_URL'] = '/test_sitemap.xml'
app.config['SIMPLE_SITEMAP_PATHS'] = {
    '/this_is_a_test': {'lastmod': '2017-04-24'}
}

@app.route('/hello')
def hello():
    return 'Hello'

# assert lazy initialization
extension.init_app(app)

client = app.test_client()

#
# Final Test Setup
####################################################################

####################################################################
# Cláusula que Permite testar manualmente o app com `python tests.py`
#
if __name__ == '__main__':
    app.run(debug=True)
#
# acesse localhost:5000/test_sitemap.xml
####################################################################

####################################################################
# Agora sim os testes que serão executados com `py.test tests.py -v`
#

def test_sitemap_uses_custom_url():
    response = client.get('/test_sitemap.xml')
    assert response.status_code == 200

def test_generated_sitemap_xml_is_valid():
    response = client.get('/test_sitemap.xml')
    xml = response.data.decode('utf-8')
    result = xmltodict.parse(xml)
    assert 'urlset' in result
    # rules are Ordered
    assert result['urlset']['url'][0]['loc'] == '/test_sitemap.xml'
    assert result['urlset']['url'][1]['loc'] == '/hello'
    assert result['urlset']['url'][2]['loc'] == '/this_is_a_test'

#
# Ao terminar o tutorial reescreva esses testes usando fixtures :)
# E é claro poderá adicionar mais testes!
###################################################################

Para que os testes acima sejam executados precisamos instalar algumas dependencias portanto o requirements-test.txt precisa deste conteúdo:

flask
pytest
xmltodict
--editable .

NOTE: ao usar --editable . no arquivo de requirements você faz com que a extensão seja auto instalada em modo de edição desta forma executamos apenas pip install -r requirements-test.txt e o pip se encarrega de rodar o python setup.py develop.

Vamos então começar a desenvolver editando o front-end da extensão que será escrito no template: flask_simple_sitemap/templates/sitemap.xml este template espera um dict paths chaveado pela location e contento sitemap tag names em seu valor. exemplo paths = {'/artigos': {'lastmod': '2017-04-24'}, ...}

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    {% for loc, data in paths.items() %}
    <url>
        <loc>{{loc|safe}}</loc>
        {% for tag_name, value in data.items() %}
            <{{tag_name}}>{{value}}</{{tag_name}}>
        {% endfor %}
    </url>
    {% endfor %}
</urlset>

E então finalmente escreveremos a classe base da extensão no arquivo flask_simple_sitemap/base.py

Lembrando de algumas boas práticas:

  • Prover um método de inicialização lazy com a assinatura init_app(self, app)
  • Impedir registro em duplicidade e inserir um registro no app.extensions
  • Ao adicionar rotas sempre usar Blueprints e namespaces (o Blueprint já se encarrega do namespace nas urls)
  • Configs devem sempre ter um prefixo, faremos isso com o get_namespace que aprendemos na parte 1

NOTE: Leia atentamente os comentários e docstrings do código abaixo.

# coding: utf-8
from flask import Blueprint, render_template, make_response


class SimpleSitemap(object):
    "Extensão Flask para publicação de sitemap"

    def __init__(self, app=None):
        """Define valores padrão para a extensão
        e caso o `app` seja informado efetua a inicialização imeditatamente
        caso o `app` não seja passado então
        a inicialização deverá ser feita depois (`lazy`)
        """
        self.config = {
            'blueprint': 'simple_sitemap',
            'url': '/sitemap.xml',
            'paths': {}
        }
        self.app = None  # indica uma extensão não inicializada

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        """Método que Inicializa a extensão e
        pode ser chamado de forma `lazy`.

        É interessante que este método seja apenas o `entry point` da extensão
        e que todas as operações de inicialização sejam feitas em métodos
        auxiliares privados para melhor organização e manutenção do código.
        """
        self._register(app)
        self._load_config()
        self._register_view()

    def _register(self, app):
        """De acordo com as boas práticas para extensões devemos checar se
        a extensão já foi inicializada e então falhar explicitamente caso
        seja verdadeiro.
        Se tudo estiver ok, então registramos o app.extensions e o self.app
        """
        if not hasattr(app, 'extensions'):
            app.extensions = {}

        if 'simple_sitemap' in app.extensions:
            raise RuntimeError("Flask extension already initialized")

        # se tudo está ok! então registramos a extensão no app.extensions!
        app.extensions['simple_sitemap'] = self

        # Marcamos esta extensão como inicializada
        self.app = app

    def _load_config(self):
        """Carrega todas as variaveis de config que tenham o prefixo `SIMPLE_SITEMAP_`
        Por exemplo, se no config estiver especificado:

            SIMPLE_SITEMAP_URL = '/sitemap.xml'

        Podemos acessar dentro da extensão da seguinte maneira:

           self.config['url']

        e isto é possível por causa do `get_namespace` do Flask utilizado abaixo.
        """
        self.config.update(
            self.app.config.get_namespace(
                namespace='SIMPLE_SITEMAP_',
                lowercase=True,
                trim_namespace=True
            )
        )

    def _register_view(self):
        """aqui registramos o blueprint contendo a rota `/sitemap.xml`"""
        self.blueprint = Blueprint(
            # O nome do blueprint deve ser unico
            # usaremos o valor informado em `SIMPLE_SITEMAP_BLUEPRINT`
            self.config['blueprint'],

            # Agora passamos o nome do módulo Python que o Blueprint
            # está localizado, o Flask usa isso para carregar os templates
            __name__,

            # informamos que a pasta de templates será a `templates`
            # já é a pasta default do Flask mas como a nossa extensão está
            # adicionando um arquivo na árvore de templates será necessário
            # informar
            template_folder='templates'
        )

        # inserimos a rota atráves do método `add_url_rule` pois fica
        # esteticamente mais bonito do que usar @self.blueprint.route()
        self.blueprint.add_url_rule(
            self.config['url'],  # /sitemap.xml é o default
            endpoint='sitemap',
            view_func=self.sitemap_view,  # usamos outro método como view
            methods=['GET']
        )

        # agora só falta registar o blueprint na app
        self.app.register_blueprint(self.blueprint)

    @property
    def paths(self):
        """Cria a lista de URLs que será adicionada ao sitemap.

        Esta property será executada apenas quando a URL `/sitemap.xml` for requisitada

        É interessante ter este método seja público pois permite que seja sobrescrito
        e é neste método que vamos misturar as URLs especificadas no config com
        as urls extraidas do roteamento do Flask (Werkzeug URL Rules).

        Para carregar URLs dinâmicamente (de bancos de dados) o usuário da extensão
        poderá sobrescrever este método ou contribur com o `SIMPLE_SITEMAP_PATHS`

        Como não queremos que exista duplicação de URLs usamos um dict onde
        a chave é a url e o valor é um dicionário completando os dados ex:

        app.config['SIMPLE_SITEMAP_PATHS'] = {
            '/artigos': {
                'lastmod': '2017-01-01'
            },
            ...
        }
        """

        paths = {}

        # 1) Primeiro extraimos todas as URLs registradas na app
        for rule in self.app.url_map.iter_rules():
            # Adicionamos apenas GET que não receba argumentos
            if 'GET' in rule.methods and len(rule.arguments) == 0:
                # para urls que não contém `lastmod` inicializamos com
                # um dicionário vazio
                paths[rule.rule] = {}

        # caso existam URLs que recebam argumentos então deverão ser carregadas
        # de forma dinâmica pelo usuário da extensão
        # faremos isso na hora de usar essa extensão no CMS de notícias.

        # 2) Agora carregamos URLs informadas na config
        # isso é fácil pois já temos o config carregado no _load_config
        paths.update(self.config['paths'])

        # 3) Precisamos sempre retornar o `paths` neste método pois isso permite
        # que ele seja sobrescrito com o uso de super(....)
        return paths

    def sitemap_view(self):
        "Esta é a view exposta pela url `/sitemap.xml`"
        # geramos o XML através da renderização do template `sitemap.xml`
        sitemap_xml = render_template('sitemap.xml', paths=self.paths)
        response = make_response(sitemap_xml)
        response.headers['Content-Type'] = 'application/xml'
        return response

NOTE: Neste exemplo usamos um método de desenvolvimento muito legal que eu chamo de:
ITF (Important Things First) onde Arquitetura, Documentação, Testes e Front End (e protótipos) são muito mais importantes do que a implementação de back end em si.
Assumimos que caso a nossa implementação seja alterada os conceitos anteriores se mantém integros com a proposta do produto.
Ordem de prioridade no projeto: 1) Definimos a arquitetura 2) Escrevemos documentação 3) Escrevemos testes 4) Implementamos front end (e protótipo) 5) back end é o menos importante do ponto de vista do produto e por isso ficou para o final! :)

O código da extensão etá disponível em http://github.com/rochacbruno/flask_simple_sitemap

Usando a extensão em nosso CMS de notícias

Agora vem a melhor parte, usar a extensão recém criada em nosso projeto existente.

O repositório do CMS está no github Precisamos do MongoDB em execução e a forma mais fácil é através do docker

➤ docker run -d -p 27017:27017 mongo

Se preferir utilize uma instância do MongoDB instalada localmente ou um Mongo As a Service.

NOTE: O modo de execução acima é efemero e não persiste os dados, para persistir use -v $PWD/etc/mongodata:/data/db.

Agora que o Mongo está rodando execute o nosso CMS.

Obtendo, instalando e executando:

➤
git clone -b extended --single-branch https://github.com/rochacbruno/wtf.git extended
cd wtf

# adicione nossa extensao nos requirements do CMS 
# sim eu publiquei no PyPI, mas se preferir instale a partir do fonte que vc escreveu
echo 'flask_simple_sitemap' >> requirements.txt

# activate a virtualenv
pip install -r requirements.txt

# execute
python run.py 

Agora com o CMS executando acesse http://localhost:5000 e verá a seguinte tela:

cms

Os detalhes dessa aplicação você deve ser lembrar pois estão nas partes 1, 2 e 3 deste tutorial.

Agora você pode se registrar novas notícias usando o link cadastro e precisará efetuar login e para isso deve se registrar como usuário do aplicativo.

Temos as seguintes urls publicads no CMS

  • '/' lista todas as noticias na home page
  • '/noticias/cadastro' exibe um formulário para incluir noticias
  • '/noticia/<id> acessa uma noticia especifica
  • '/admin' instancia do Flask Admin para adminstrar usuários e o banco de dados

Agora vamos incluir a extensão flask_simple_sitemap que criamos e adicionar as URLs das noticias dinâmicamente.

Edite o arquico wtf/news_app.py incluindo a extensão flask_simple_sitemap e também adicionando as URLs de todas as noticias que existirem no banco de dados.

# coding: utf-8
from os import path
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_security import Security, MongoEngineUserDatastore
from flask_debugtoolbar import DebugToolbarExtension

###############################################
# 1) importe a nossa nova extensão
from flask_simple_sitemap import SimpleSitemap

from .admin import configure_admin
from .blueprints.noticias import noticias_blueprint
from .db import db
from .security_models import User, Role
from .cache import cache

##############################################
# 2) importe o model de Noticia
from .models import Noticia


def create_app(mode):
    instance_path = path.join(
        path.abspath(path.dirname(__file__)), "%s_instance" % mode
    )

    app = Flask("wtf",
                instance_path=instance_path,
                instance_relative_config=True)

    app.config.from_object('wtf.default_settings')
    app.config.from_pyfile('config.cfg')

    app.config['MEDIA_ROOT'] = path.join(
        app.config.get('PROJECT_ROOT'),
        app.instance_path,
        app.config.get('MEDIA_FOLDER')
    )

    app.register_blueprint(noticias_blueprint)

    Bootstrap(app)
    db.init_app(app)
    Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role))
    configure_admin(app)
    DebugToolbarExtension(app)
    cache.init_app(app)

    ############################################
    # 3) Adicionane as noticias ao sitemap
    app.config['SIMPLE_SITEMAP_PATHS'] = {
        '/noticia/{0}'.format(noticia.id): {} # dict vazio mesmo por enquanto!
        for noticia in Noticia.objects.all()
    }

    ############################################
    # 4) Inicialize a extensão SimpleSitemap
    sitemap = SimpleSitemap(app)

    return app

Agora execute o python run.py e acesse http://localhost:5000/sitemap.xml

Você verá o sitemap gerado incluindo as URLs das notícias cadastradas!

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
  <loc>/noticia/58ffe998e138231eef84f9a7</loc>
</url>
<url>
  <loc>/noticias/cadastro</loc>
</url>
<url>
  <loc>/</loc>
</url>
...
# + um monte de URL do /admin aqui
</urlset>

NOTE: Funcionou! legal! porém ainda não está bom. Existem algumas melhorias a serem feitas e vou deixar essas melhorias para você fazer!

Desafio What The Flask!

1) Melhore a geração de URLs do CMS

Você reparou que a URL das notícias está bem feia? /noticia/58ffe998e138231eef84f9a7 não é uma boa URL Para ficar mais simples no começo optamos por usar o id da notícia como URL mas isso não é uma boa prática e o pior é que isso introduz até mesmo problemas de segurança.

Você conseguer arrumar isso? transformando em: /noticia/titulo-da-noticia-aqui ?

Vou dar umas dicas:

Altere o Model:

  • Altere o model Noticia em: https://github.com/rochacbruno/wtf/blob/extended/wtf/models.py#L5.
  • Insira um novo campo para armazenar o slug da notícia, o valor será o título transformado para lowercase, espaços substituidos por -. Ex: para o título: 'Isto é uma notícia' será salvo o slug: 'isto-e-uma-noticia'.
  • Utilize o método save do MongoEngine ou se preferir use signals para gerar o slug da notícia.
  • Utilize o módulo awesome-slugify disponível no PyPI para criar o slug a partir do título.

Altere a view:

Altere as urls passadas ao SIMPLE_SITEMAP_PATHS usando o slug ao invés do id.

2) Adicione data de publicação nas notícias

Reparou que o sitemap está sem a data da notícia? adicione o campo modified ao model Noticia e faça com que ele salve a data de criação e/ou alteração da notícia.

Queremos algo como:

    app.config['SIMPLE_SITEMAP_PATHS'] = {
        '/noticia/{0}'.format(noticia.slug): {
            'lastmod': noticia.modified.strftime('%Y-%m-%d')
        }
        for noticia in Noticia.objects.all()
    }

Para gerar no sitemap:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
  <loc>/noticia/titulo-da-noticia</loc>
  <lastmod>2017-04-25</lastmod>
</url>
...

3) Crie uma opção de filtros na extensão simple_sitemap

Uma coisa chata também é o fato do sitemap.xml ter sido gerado com esse monte de URL indesejada. As URLs iniciadas com /admin por exemplo não precisam ir para o sitemap.xml.

Implemente esta funcionalidade a extensão:

DICA: Use regex import re

app.config['SIMPLE_SITEMAP_EXCLUDE'] = [
    # urls que derem match com estes filtros não serão adicionadas ao sitemap
    '^/admin/.*'
]

DESAFIO: Após implementar as melhorias inclua nos comentários um link para a sua solução, pode ser um fork dos repositórios ou até mesmo um link para gist ou pastebin (enviarei uns adesivos de Flask para quem completar o desafio!)


O diff com as alterações realizadas no CMS encontra-se no github.com/rochacbruno/wtf
A versão final da extensão SimpleSitemap está no github.com/rochacbruno/flask_simple_sitemap
A versão final do CMS app está no github.com/rochacbruno/wtf

Se você está a procura de uma extensão para sitemap para uso em produção aconselho a flask_sitemap


END: Sim chegamos ao fim desta quarta parte da série What The Flask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas partes iremos efetuar o deploy de aplicativos Flask. Acompanhe o PythonClub, o meu site e meu twitter para ficar sabendo quando a próxima parte for publicada.


PUBLICIDADE: Iniciarei um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este formulário pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente.


PUBLICIDADE 2: Também estou escrevendo um livro de receitas Flask CookBook através da plataforma LeanPub, caso tenha interesse por favor preenche o formulário na página do livro


PUBLICIDADE 3: Inscreva-se no meu novo canal de tutoriais

Muito obrigado e aguardo seu feedback com dúvidas, sugestões, correções etc na caixa de comentários abaixo.

Abraço! "Python é vida!"

por Bruno Cezar Rocha em 26 de April de 2017 às 12:00

April 23, 2017

PythonClub

Configurando OpenShift com Python 3.5 + Flask + Gunicorn

Configurando OpenShift com Python 3.5

Introdução

O OpenShift é uma plataforma de PasS que possibilita aos desenvolvedores "subir" aplicações na nuvem de uma maneira simples e rápida. Ele funciona a partir de gears(engrenagens) que representam máquinas que irão rodar as aplicações. Dentro de cada gear é possível instalar serviços, os são chamados de "cartridges".

Existem 3 planos:

  • Online (gratuito, com três gears)
  • Enterprise (pago com suporte)
  • Origin (versão da comunidade e pode ser utilizado livremente)

Um problema que me deparei ao utilizar o Openshift é que ele não possui um cartridge com Python3.5. Porém existe uma forma um pouco mais complicada de resolver esse problema.

Após fazer seu cadastro no OpenShift e instalar o client tools que contém as ferramentas necessárias para configurar nossa aplicação.

Após tudo isso vamos colocar a mão na massa, abra seu terminal e vamos lá.

Criando a aplicação

rhc app create <app-name> https://raw.githubusercontent.com/Grief/openshift-cartridge-python-3.5/master/metadata/manifest.yml diy-0.1

Substituindo "<app-name>" pelo nome de sua aplicação. O arquivo manifest.yml criado por Changaco(github) e "forkeado" por Grief(github) contém as configurações de um cartridge customizado que contém o python 3.5.

Para os curiosos o conteúdo do arquivo

---
Name: python
Cartridge-Short-Name: PYTHON
Display-Name: Only Python
Description: 'An embedded cartridge that provides only python, nothing else.'
Version: '3.5.0'
Versions: ['3.5.0', '2.7.11']
License: The Python License
License-Url: http://docs.python.org/3/license.html
Vendor: python.org
Cartridge-Version: 0.0.2
Cartridge-Vendor: praisebetoscience
Categories:
- service
- python
- embedded
Website: https://github.com/praisebetoscience/openshift-cartridge-python-3.5
Help-Topics:
  Developer Center: https://www.openshift.com/developers
Provides:
- python
Publishes:
Subscribes:
  set-env:
    Type: ENV:*
    Required: false
  set-doc-url:
    Type: STRING:urlpath
    Required: false
Scaling:
  Min: 1
  Max: -1
Version-Overrides:
  '2.7.11':
    Display-Name: Python 2.7
    License: The Python License, version 2.7
    Provides:
    - python-2.7
    - python
    - python(version) = 2.7
  '3.5.0':
    Display-Name: Python 3.5
    License: The Python License, version 3.5
    Provides:
    - python-3.5
    - python
    - python(version) = 3.5

Após isso sua aplicação já estárá executando, caso deseje acessar o endereço da mesma deverá ser http://<app-name>-.rhcloud.com. Você verá que a página do seu projeto não é nada mais do que o diy (Dot It Yourself), que é uma aplicação Ruby de exemplo que você pode alterar, e é o que vamos fazer.

Se você acessar o diretório do seu projeto verá que existe um diretório ".openshift", dentro desse diretório existe um outro diretório chamado "action_hooks", e dentro desse diretório existem dois arquivos "start" e "stop".

  • "<app-name>/.openshift/action_hooks/start"
  • "<app-name>/.openshift/action_hooks/stop"

Os dois arquivos são respectivamente os comandos para "subir" e "pausar" sua aplicação.

Flask

Vamos criar um projeto de exemplo, bem simples, que apenas nos retorne a versão do python utilizada. Primeiramente vamos criar nosso requirements.txt, com gunicorn e o flask.

"requirements.txt"

gunicorn
flask

Depois disso vamos criar o arquivo app.py que conterá nossa aplicação.

"app.py"

import sys
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return sys.version

Após isso basta fazer o commit de suas alterações.

shell git add . git commit -am 'Minhas alterações'

Após isso você verá que sua aplicação não está rodando, pois ainda não alteramos os arquivos "start" e "stop".

Configurando o Gunicorn no Start e Stop

O projeto diy do openshift nos deixa uma variável de ambiente $OPENSHIFT_DIY_IP com o IP da máquina, dessa forma podemos passar a variável e porta ao gunicorn.

"start"

#!/bin/bash
nohup $HOME/python/usr/bin/pip3 install -r $OPENSHIFT_REPO_DIR/requirements.txt
cd $OPENSHIFT_REPO_DIR
nohup $HOME/python/usr/bin/gunicorn app:app --bind=$OPENSHIFT_DIY_IP:8080 |& /usr/bin/logshifter -tag diy &

A primeira linha é o Shebang, o que significa que esse arquivo será executado pelo bash. Na segunda linha vemos nohup, que executa os comandos em uma sessão separada, vemos logo apóes vemos o uma chamada ao pip para instalar nossas dependências. Na terceira linha vemos o nohup, e depois o gunicorn inicializa nossa aplicação flask.

Isso só funciona pois o cartridge customizado instala o python3.5 dentro da pasta home do servidor do openshift.

"stop"

#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH

# The logic to stop your application should be put in this script.
if [ -z "$(ps -ef | grep gunicorn | grep -v grep)" ]
then
    client_result "Application is already stopped"
else
    kill `ps -ef | grep gunicorn | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

Podemos ver que o comando ps procura algum processo do gunicorn, caso ele exista o kill será chamado.

Após isso, é só fazer o commit das alterações e você verá sua aplicação rodando.

Espero que o post ajude a quem quer subir alguma aplicação com python3.5 e só tem o heroku como opção.

Referências

por Horácio Dias em 23 de April de 2017 às 23:37

April 21, 2017

Allison Azevedo

python-vindi

Acabei de subir o primeiro release (0.1.0) do python-vindi, esse é o primeiro projeto que usa o python-simple-rest-client como base (Python 3.5+).

Github: https://github.com/allisson/python-vindi


por Allisson Azevedo em 21 de April de 2017 às 15:57

April 18, 2017

Allison Azevedo

python-simple-rest-client

Acabei de subir o primeiro release (0.1.0) do python-simple-rest-client, essa lib necessita do python 3.5+ e no próximo release vai vir com suporte ao asyncio (via aiohttp).

Github: https://github.com/allisson/python-simple-rest-client

Documentação: http://python-simple-rest-client.readthedocs.org/


por Allisson Azevedo em 18 de April de 2017 às 15:29

Programação Assíncrona com Asyncio

No dia 25/03/2015 eu tive a oportunidade de apresentar a palestra Programação Assíncrona com Asyncio no PythonDay Campina Grande, o vídeo, slides e código estão disponíveis logo abaixo.

Vídeo:

Slides: http://allisson.github.io/slides/2017/pythonday_cg/
Códigos de exemplo: https://github.com/allisson/pythonday-campina-grande-2017


por Allisson Azevedo em 18 de April de 2017 às 15:20

April 16, 2017

Magnun Leno

Hack ‘n’ Cast v1.6 - Espresso #002: Colisão SHA-1

Corram todos para as montanhas, o SHA-1 é oficialmente inseguro!

Baixe o episódio e leia o shownotes

por Magnun em 16 de April de 2017 às 19:12

March 29, 2017

Bruno Cezar Rocha

Consumindo e Publicando web APIs - PyData São Paulo - 2017

Consumindo e publicando web APIs apresentado no PyData SP no auditório da NuBank em 28 de Março de 2017

  1. O que são web APIs
  2. Consumindo web APIs com Python
  3. O que fazer com os dados?
  4. Publicando web APIs com Python e Flask.

#python #flask #sanic #flasgger


http://github.com/rochacbruno/flasgger




por Bruno Rocha em 29 de March de 2017 às 00:58

March 14, 2017

Diego Garcia

Criando Um Aplicativo De Linha De Comando Com Python

Com certeza você já uso algum aplicativo de linha de comando, seja dos mais simples (como por exemplo o echo), ou dos mais sofisticados (como é o caso do cURL). O fato é, todo programador deveria criar pelo menos uma vez um aplicativo de linha de comando, seja para fins de estudo, ou até mesmo para se tornar uma grande ferramenta. Com python, criar aplicativos de linha de comando é algo muito simples e produtivo.

Hello World

Antes de nos aprofundarmos no assunto, faremos um simples Hello World utilizando o módulo argparse que é built-in do python.

# cli.py
from argparse import ArgumentParser


parser = ArgumentParser()
parser.add_argument('name', help='say your name')

args = parser.parse_args()
print('Hello {}'.format(args.name))

Resumindo o código anterior, criamos um parser e nesse parser adicionamos um argumento posicional (o argumento name). Depois convertemos os argumentos da linha de comando no objeto args e mostramos uma mensagem utilizando o valor passado para o argumento name. O resultado será o seguinte:

$ python cli.py World
Hello World

Pode parecer mais complicado do que simplesmente recuperar os valores de sys.argv, porém, é algo muito mais poderoso. Um exemplo disso é o fato de que, esse código da forma como está, já possui um help bem intuitivo dos possíveis comandos aceitos:

$ python cli.py -h
usage: cli.py [-h] name

positional arguments:
  name        say your name

optional arguments:
  -h, --help  show this help message and exit

Um pouco sobre o Argparse

O argparse é um módulo que foi adicionado a standard library do python a partir da versão 2.7 substituindo seu antecessor, o módulo optparse. Ele foi projetado para criar aplicativos de linha de comando de forma amigável e descomplicada.

O parser

O principal componente do módulo argparse é a classe ArgumentParser. A partir de uma instância de ArgumentParser é possível determinar o comportamento de linha de comando do aplicativo. Na criação do parser é possível informar alguns parâmetros relativos ao aplicativo, sendo que alguns deles servem para customizar a mensagem de help gerada automaticamente, como é o caso do parâmetro description:

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser(description='Powerful command-line tool')
>>> parser.parse_args(['-h'])
usage: [-h]

Powerful command-line tool

optional arguments:
  -h, --help  show this help message and exit

Note que é possível informar para o parser os argumentos que ele deve parsear, através do método parse_args. Esse método espera uma lista de strings como parâmetro e caso essa não seja informada, recupera a lista de argumentos através do sys.argv, ou seja, os argumentos de linha de comando.

Argumentos

Com vimos no primeiro exemplo, é possível adicionar argumentos ao parser, sejam eles posicionais (e obrigátorios), ou opcionais.

Argumentos posicionais

Os argumentos posicionais, são argumentos obrigatórios que devem ser informados na ordem em que foram declarados. Veja um exemplo de uma simples soma feita através da linha de comando:

# cli.py
from argparse import ArgumentParser


parser = ArgumentParser()
parser.add_argument('first_number', type=int)
parser.add_argument('second_number', type=int)

args = parser.parse_args()
print('{} + {} = {}'.format(
    args.first_number,
    args.second_number,
    args.first_number + args.second_number
))

O exemplo de uso do código anterior seria algo como:

$ python cli.py 2 5
2 + 5 = 7

Não se preocupe com o type no exemplo, veremos o que isso significa mais adiante

Argumentos opcionais

Os argumentos opcionais são declarados com um ou dois hífens no prefixo do nome (e.g. -f, --foo) e não dependem de uma posição especifica para serem informados. Usando o exemplo anterior, vamos adicionar um argumento opcional para determinar se o output do comando deve ou não ser verboso:

# cli.py
from argparse import ArgumentParser


parser = ArgumentParser()
parser.add_argument('first_number', type=int)
parser.add_argument('second_number', type=int)
parser.add_argument('--verbose', action='store_true')

args = parser.parse_args()
result = args.first_number + args.second_number
if args.verbose:
    print('{} + {} = {}'.format(
        args.first_number,
        args.second_number,
        result
    ))
else:
    print(result)

Desta forma, possibilitamos saidas diferentes da nossa aplicação de acordo com a presença ou não do argumento --verbose.

$ python cli.py 2 5
7
$ python t.py 2 5 --verbose
2 + 5 = 7

Não se preocupe com o action no exemplo, veremos o que isso significa mais adiante

Podemos simplificar aindas mais o argumento --verbose criando uma opção encurtada dele. O método add_argument do ArgumentParser aceita uma lista de nomes do argumento, sendo assim, basta adicionar as opções na criação do argumento:

parser.add_argument('-v', '--verbose', action='store_true')

E inclusive, essa alteração já reflete no help da aplicação:

$ python cli.py -h
usage: cli.py [-h] [-v] first_number second_number

positional arguments:
  first_number
  second_number

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose

Vale ressaltar que essa opção só faz sentido para argumentos opcionais.

Types

Por padrão, o valor de todos os argumentos são interpretados como string, porém, é possível determinar um tipo para esses valores, através do parametro type do método add_argument como já vimos anteriormente.
Ao determinar um type para um argumento, será executado um type-checking no momento do parse_args() para garantir que o valor do argumento é do tipo especificado:

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser()
>>> parser.add_argument('number', type=int)
>>> args = parser.parse_args(['a'])
usage: [-h] number
: error: argument number: invalid int value: 'a'

Qualquer callable pode ser usado como type, pontencializando ainda mais o uso de tipos nos argumentos, e.g.

# cli.py
import argparse


def odd(n):
    n = int(n)
    if n % 2 == 1:
        return n
    raise argparse.ArgumentTypeError('{} is not an odd number'.format(n))


parser = argparse.ArgumentParser()
parser.add_argument('odd_number', type=odd)
args = parser.parse_args()

Neste exemplo, se pasarmos um numero par para o argumento odd_number teremos uma mensagem de erro indicando que esse numero não é um numero impar.

$ python cli.py 2
usage: [-h] odd_number
: error: argument odd_number: 2 its not an odd number

Actions

É possível determinar ações para determinados argumentos sendo a mais comum a ação store que basicamente armazena o valor passado para o argumento. Esta é a ação default para qualquer argumento caso outro tipo de ação não seja informado.
Existem outros tipos de ações, veremos algumas delas.

store_true

A action store_true basicamente armazena o valor True caso o argumento seja informado (como vimos anteriormente no exemplo do argumento --verbose).

append

A action append armazena uma lista dos valores passados para o mesmo argumento, e.g.:

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser()
>>> parser.add_argument('--file', action='append')
>>> parser.parse_args('--file 1.txt --file 2.txt --file 3.txt'.split())
Namespace(file=['1.txt', '2.txt', '3.txt'])

count

A action count armazena um contador de vezes em que um argumento foi usado (útil para determinar niveis de verbosidade), e.g.:

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser()
>>> parser.add_argument('--verbose', '-v',  action='count')
>>> parser.parse_args(['-vvvv'])
Namespace(verbose=4)

Casos mais complexos

Normalmente, no desenvolvimento de um aplicativo cli acabamos tendo que lidar com casos mais complexos, e não somente um comando simples com alguns parâmetros. Esses aplicativos tendem a crescer e lidar com outros subcomandos, como por exemplo o git (add, commit, branch, etc):

Iremos criar um aplicativo simples com dois subcomandos, o subcomando read e o subcomando write.

A melhor maneira de lidar com subcomandos é criar SubParsers especificos para cada comando que o aplicativo irá lidar.

SubParsers

Subparsers são parsers independentes, com suas próprias caracteristicas mas que deviram de um parser principal. Vamos começar nosso aplicativo de exemplo criando seu parser principal e seus dois subparsers:

# cli.py
from argparse import ArgumentParser


parser = ArgumentParser()

subparsers = parser.add_subparsers()
r_parser = subparsers.add_parser('read', help='Commands to read data')
w_parser = subparsers.add_parser('write', help='Commands to write data')

parser.parse_args()

Se dermos uma olhada no help gerado pelo python, já veremos instruções de uso para os subcomandos:

$ python cli.py -h
usage: cli.py [-h] {read,write} ...

positional arguments:
  {read,write}
    read        Commands to read data
    write       Commands to write data

optional arguments:
  -h, --help    show this help message and exit

Iremos agora incrementar um pouco mais nosso aplicativo adicionando argumentos para cada subcomando:

# cli.py
from argparse import ArgumentParser

parser = ArgumentParser()
subparsers = parser.add_subparsers(help='Sub commands')

r_parser = subparsers.add_parser('read', help='Commands to read data')
r_parser.add_argument('origin', help='File origin')
r_parser.add_argument('--head', type=int, default=0, help='Read only N lines')

w_parser = subparsers.add_parser('write', help='Commands to write data')
w_parser.add_argument('destination', help='Destination file')
w_parser.add_argument(
    '--upper',
    action='store_true',
    help='Write all in UPPERCASE'
)

if __name__ == '__main__':
    args = parser.parse_args()

Agora é possível ter um help geral do aplicativo e um help para cada subcomando:

$ python cli.py -h

usage: cli.py [-h] {read,write} ...

positional arguments:
  {read,write}  Sub commands
    read        Commands to read data
    write       Commands to write data

optional arguments:
  -h, --help    show this help message and exit
$ python cli.py read -h

usage: cli.py read [-h] [--head HEAD] origin

positional arguments:
  origin       File origin

optional arguments:
  -h, --help   show this help message and exit
  --head HEAD  Read only N lines
$ python cli.py write -h

usage: cli.py write [-h] [--upper] destination

positional arguments:
  destination  Destination file

optional arguments:
  -h, --help   show this help message and exit
  --upper      Write all in UPPERCASE

set_defaults

Um problema ao se utilizar subparsers é que ao executar o parser.parser_args() não é possível determinar qual subcomando foi requisitado, somente os argumentos do subparser:

$ python cli.py read foo.txt --head 2
Namespace(head=2, origin='foo.txt')

Para contornar esse comportamento, é possível determinar valores default para um subparser através do método set_default, que espera um conjunto de argumentos nomeados (**kwargs):

# cli.py
r_parser.set_defaults(command='read')
...
args = parser.parse_args()
print('subcommand: ', args.command)

Para o exemplo anterior, ao chamar o cli.py na linha de comando passando o subcomando read teriamos a seguinte saida:

$ python cli.py read foo.txt
subcommand: read

Criando handlers para os subparsers

Conhecendo esse truque e sabendo que o valor default pode ser de qualquer tipo (inclusive um callable), podemos criar handlers para os nossos subcommands.
Vamos mudar nosso código de exemplo adicionado duas funções, a função read e a função write:

def read(args):
    print('call read with: {}'.format(args))


def write(args):
    print('call write with: {}'.format(args))

Agora iremos usar essas funções como um valor default em nossos subparser:

r_parser.set_defaults(handler=read)
w_parser.set_defaults(handler=write)

E por fim, iremos usar essas funções após a leitura dos argumentos da linha de comando:

args = parser.parse_args()
args.handler(args)

Ao executarmos nosso cli passando um subcomando, podemos ver que a saída do comando indica que nosso handler foi chamado:

$ python cli.py write foo.txt --upper
call write with: Namespace(destination='foo.txt', handler=<function write at 0x100de9140>, upper=True)

Isso abre a possíbilidade de criar aplicativos realmente complexos de forma simples e organizada.

Testando Parsers

“I don’t care if it works on your machine! We are not shipping your machine!” — Vidiu Platon.

Software sem testes é um software que não deve ser entregue, ou seja, é um software incompleto. Já sabemos como criar complexas soluções de aplicativos de linha de comando, só nos falta saber como criar testes para essa aplicações.
Usando o ArgumentParser do python não existe muitos mistérios em como realizar os testes, para utilizar o método parse_args() e verificar a forma como os argumentos foram processados:

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser()

>>> subparser = parser.add_subparsers()
>>> x_parser = subparser.add_parser('x')
>>> x_parser.set_defaults(foo='bar')

>>> args = parser.parse_args(['x'])
>>> assert args.foo == 'bar'
>>> assert args.foo == 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Algumas quebras de linha foram adicionadas para melhorar a leitura

Você pode tranquilamente utilizar esse recurso dentro da sua suite de testes.

Distribuindo seu aplicativo

Com o aplicativo já escrito (e devidamente testado), chegou a hora de distribuir para o mundo. Não irei entrar em detalhes sobre como prepara o arquivo setup.py de forma correta (talvez em outro post), porém irei comentar sobre uma configuração especifica, os entry_points.

Entrypoint

Existem diferentes tipos de entrypoints disponíveis para serem usados, em nosso exemplo iremos usar o mais comum deles, o console_script, com isso, iremos determinar como a nossa aplicação deverá ser chamada na linha de comando após a instalação:

# setup.py
from setuptools import setup, find_packages

author_name = 'Diego Garcia'
author_email = 'drgarcia1986@gmail.com'

setup(
    name='cli',
    version='0.0.1',
    description='Cool cli',
    long_description='Cool cli from http://www.diego-garcia.info/,
    url='https://github.com/drgarcia1986/cli',
    author=author_name,
    author_email=author_email,
    maintainer=author_name,
    maintainer_email=author_email,
    license='MIT',
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Topic :: System :: Shells',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
    ],
    keywords='cli',
    download_url='https://github.com/drgarcia1986/cli/archive/master.zip',
    packages=find_packages(exclude=['tests*']),
    install_requires=[],
    entry_points={'console_scripts': ['cli = cli:main']},
    platforms='windows linux',
)

O detalhe importante fica por conta da linha entry_points={'console_scripts': ['cli = cli:main']}, onde determinamos que o entry point de console script da nossa aplicação, será a função main dentro do arquivo cli, para quando a aplicação for chamada como cli na linha de comando.
Essa função main nada mais é do que uma função que chama o método parser_args(), e.g.:

def main():
    args = parser.parse_args()
    args.handler(args)

Pronto, ao realizar a instalação da aplicação podemos utiliza-la na linha de comando apenas chamando o comando cli:

$ cli -h

usage: cli [-h] {read,write} ...

positional arguments:
  {read,write}  Sub commands
    read        Commands to read data
    write       Commands to write data

optional arguments:
  -h, --help    show this help message and exit

Third party libs

Existem algumas bibliotecas opensource que podem facilitar a vida de quem pretende escrever um aplicativo de linha de comando, vou listar algumas:

Você pode testá-las e concluir se alguma se encaixa melhor no seu problema ou se a standard library do python já é o suficiente.

Referências
Argparse Official Documentation
Argparse tutorial
Python Argparse Cookbook
argparse – Command line option and argument parsing

por Diego Garcia em 14 de March de 2017 às 01:00

March 06, 2017

Thiago Avelino

Qual o glamour em ser CEO?

The Wolf of Wall Street (2013)

Antes de entrar no assunto do titulo vou contextualizar o porque estou escrevendo esse blogpost.

Eu (Avelino) a anos acompanho minha rede de contato no LinkedIn e via que muitas pessoas estava como (ou se tornou à pouco tempo) CEO (Chief Executive Officer, que significa Diretor Executivo) de empresa, isso me surgiu curiosidade em saber qual empresa essas pessoas tinham se tornado CEO, para minha surpresa a maioria tinha se tornado CEO de uma empresa e continuava trabalhando em outra empresa, muitas sendo assalariado.

Com isso surgiu algumas dúvidas dentro da minha cabeça:

  • “Como ser Diretor Executivo de uma empresa e ocupar um cargo em outra empresa?”
  • “Como ter tempo para ser Diretor Executivo e ocupar um cargo em outra empresa?”
  • “Como fazer relacionamento com sociedade, acionistas, sócios, funcionários, clientes, governo e etc?”

Analisando esse comportamento eu acho que seria interessante compartilhar meu pensamento sobre o falso glamour de empreender e ser CEO.

O que é CEO e qual seu papel?

CEO é a sigla para Chief Executive Officer, que significa Diretor Executivo. É o cargo de mais alto nível a ser ocupado em uma companhia, o responsável pelo gerenciamento de toda a organização.

O seu papel é, além de aplicar as decisões na prática ser aquele que lidera as discussões, promove novas maneiras de pensar e que é o comunicador por excelência de uma organização. É também sua função trabalhar a relação com a imprensa e com tudo que envolva a companhia, mas que aconteça fora dela.

O CEO, no entanto, não tem "passe livre" (você acha que vai colocar o seu salário de R$ 70.00,00 por ser CEO?) para fazer o que quiser. Ele executa as decisões, mas responde diretamente ao conjunto de diretores da empresa. Na maior parte das companhias, existe um diretor que supervisiona o trabalho do CEO (pensando em grandes companhia), a quem o executivo normalmente se dirige preferencialmente.

Glamour

Glamour tem sua origem na palavra "Grammar", e significa uma qualidade extraordinária em um determinado indivíduo, fazendo com que ele pareça muito atraente.

sonho não é realidade

Trazendo o termo "Glamour" para dentro do contexto do blogpost, estamos falando de uma pessoa que tem qualidade extraordinária de se relacionar com sociedade, acionistas, sócios, funcionários, clientes, governo e etc, qualquer pessoa é apta a ter esse perfil? Respondendo esse questionamento eu diria que “Sim, mas precisa de experiência e isso só vem com o tempo”.

Meu caso em particular

Para quem não me conhece sou CTO (e fundador) da Nuveo (nuvem inteligente que revolucionará a forma como as pessoas guardam, utilizam e encontram informações) fundada em 2015. Após passar por uma crise em uma das empresas que eu tinha resolvi tirar algumas semana para pensar como sair do buraco que estava, depois de alguns dias pensando como resolver os problemas financeiros (mas nunca pensando a curto prazo e sim a logo prazo) cheguei na ideia base da Nuveo.

Eu sei que não tenho (e não quero ter) perfil para ser CEO de uma companhia, dado esse cenário liguei para o atual CEO da Nuveo (José Flavio Pereira) comentando a ideia e como imaginaria monetizar (lógico que essa ideia mudou depois de alguns meses pois percebemos que não era possível tracionar no mercado e muito menos escalar), Flavio me pediu alguns minutos para pensar e iria me retornar a ligação. Aproximadamente 30 minutos depois, ele me retorna com duas pessoas querendo investir na empresa (que nem tinha nascido), ou seja, eu tinha achado o CEO da Nuveo (bastava ele aceitar), logicamente ele aceitou e segue na empreitada até hoje.

Responda para você mesmo

  • O que adiantaria assumir o cargo de CEO sendo que não tenho perfil?
  • Ou você quer viver de aparencia em ser chamado de CEO?
  • O que lhe motiva continuar empreendendo?
  • Quantas vezes você já pensou em desistir?

Empreender é viver no risco, precisamos minimizar ao máximo nosso risco.

por Avelino em 06 de March de 2017 às 21:03

March 03, 2017

Magnun Leno

Hack ‘n’ Cast v1.5 - Contribuindo com o Mundo FOSS

Se você já desejou contribuir com o mundo do Free or Open Source Software mas nunca se sentiu capaz, esse episódio é pra você!

Baixe o episódio e leia o shownotes

por Magnun em 03 de March de 2017 às 03:30

March 02, 2017

Aprenda Python

Como passar conteúdo para o Javascript?

**tl;dr;** envie conteúdo pelo atributo `data-*` do HTML e pegue o resultado no Javascript com `getAttribute()`. Essa dúvida é muito comum para quem está começando a programar para web. Nosso cenário de exemplo é uma aplicação Django que gera uma página HTML completa, sem ajax, e precisa mandar um código de produto para o Javascript. Vou mostrar de maneira bem objetiva como "passar" o

por Vinicius Assef (noreply@blogger.com) em 02 de March de 2017 às 18:39

March 01, 2017

Thiago Avelino

Parabéns pelo post inspirador Le.

Parabéns pelo post inspirador Le.

Sair da zona de conforto realmente não é fácil, mas super gratificante quando paramos para analisar pós turbulência.

por Avelino em 01 de March de 2017 às 19:21

February 26, 2017

Aprenda Python

Simplificando comandos do git com alias

**tl;dr**: use _aliases_ do git para simplificar comandos complicados e digitar menos. Quem usa `git`, sabe que ele é muito poderoso, mas alguns comandos são complicados e nada intuitivos. Se você for como eu, sempre precisa dar uma conferida no manual antes de, por exemplo, desfazer um commit. Talvez isso seja uma característica planejada. Afinal, você precisa ter certeza antes de destruir

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2017 às 02:33

February 24, 2017

Aprenda Python

Diretório __pycache__ e arquivos .pyc

Quem nunca se incomodou com os diretórios `__pycache__` criados pelo Python? Ou com os arquivos `.pyc` criados pelo Python legado (Python 2)? Eles incomodam, mas para que servem? Os diretórios `__pycache__` guardam os arquivos `.pyc`, que são a versão compilada dos módulos importados por seu programa. Sendo bem simplista, o Python verifica se já existe uma versão no formato `.pyc` antes de

por Vinicius Assef (noreply@blogger.com) em 24 de February de 2017 às 20:29

February 16, 2017

Magnun Leno

February 02, 2017

Magnun Leno

January 30, 2017

Lauro Moura

C# – sizeof vs Marshal.SizeOf

Depois do PySide – ainda no INDT como já falei em outros posts antigos – e dos bindings JS para o EFL, no meu trabalho atual estamos fazendo bindings para C#, mais especificamente para o Mono.

A princípio é relativamente simples usar código C a partir de C#. De forma resumida, basta declarar uma função em C# dizendo de que biblioteca ele deve importar a função nativa – pense em dlopen/dlsym – e então invocar a função. O Mono cuida de converter os tipos entre o código gerenciado e a função nativa, tanto os parâmetros como o retorno da função. Por exemplo, direto do guia do Mono para interoperabilidade:

[DllImport ("libc.so")]
private static extern int getpid ();

Lógico que isso é apenas o caso mais simples. Dependendo das peculiaridades do tipo a ser convertido, você pode precisar colocar mais informações para orientar o Mono nessa conversão, como o layout das estruturas, o formato de conversão de strings, ou mesmo uma conversão customizada.

Numa dessas customizações tive problemas durante a chamada de algumas funções, onde misteriosamente a pilha de chamada estava sendo corrompida. Depois de alguns testes, vi que as funções que corrompiam os dados envolviam uma estrutura que era passada por valor como argumento. De forma análoga às funções, onde a assinatura que você declara em C# é uma cópia da assinatura nativa e representa o “layout” daquela função na memória, com as estruturas você também faz o mesmo em C#. No caso, essa estrutura era declarada manualmente em C# da seguinte forma:

struct FooBar {
  IntPtr obj;
  bool something;
  bool another_thing;
  int size;
}

Enquanto que em C a estrutura tinha o seguinte formato:

struct Foo_Bar {
  Obj *obj;
  byte something : 1; // Na pratica é um typedef p/ byte
  byte another_thing : 1;
  int size;
}

A princípio tudo parece correto, já que bool no C# é armazenado no espaço de 1 byte, e apesar do bit field em C, cada field “byte” no C ocupava também 1 byte no final, devido ao packing da estrutura.

Ao realizar mais testes, inicialmente usando sizeof no C# e no C, o tamanho e os offsets dos campos estavam iguais entre o C# e C. Foi então que entrou em cena do Marshal.SizeOf. Marshal é uma classe do C# responsável por cuidar da conversão (marshalling) de tipos entre o código gerenciado e o código nativo.

O problema com sizeof era que ele media o uso de memória gerenciada dos tipos. E, curiosamente, o tipo booleano de C# por padrão difere no espaço utilizado entre a memória gerenciada (1) e memória nativa (4), este último corretamente informado pelo Marshal.SizeOf. A solução então foi indicar para o compilador para usar apenas 1 byte ao converter os campos booleanos, da seguinte forma:

struct FooBar {
  IntPtr obj;
  [MarshalAsAttribute(UnmanagedType.U1)] bool something;
  [MarshalAsAttribute(UnmanagedType.U1)] bool another_thing;
 int size;
}

Feito isso, o problema foi corrigido e todos viveram felizes até o próximo bug. 🙂


por lauro em 30 de January de 2017 às 02:43

January 21, 2017

JungleCoders

Migrando o servidor de chat para Python 3.6

Na época do lançamento do Python 3.4, eu estava tão contente com a integração do Asyncio que escrevi um servidor de chat aqui. O tempo passou e novas versões do Python foram lançadas. Resolvi então migrar o servidor para Python 3.6.

Uma das grandes mudanças que ocorreram no Python 3.5, foi o suporte a async e await para substituir @asyncio.corroutine e yield from respectivamente. Esta pequena mudança por si só já facilita em muito a leitura do código, que ficou mais leve. Mas uma das principais mudanças do Python 3.6 são as f-strings que facilitam a formação de mensagens.

Primeiro, vamos preparar o ambiente. É preciso instalar o Python 3.6. Se você utiliza Windows, basta baixar o pacote no site da Python.org.

Ubuntu 16.10

Se você utiliza Ubuntu 16.10, ainda precisa baixar os fontes e compilar... mas seguindo a recomendação de amigos do Telegram, resolvi experimentar com o pyenv!

Para instalar no Ubuntu, baixe o install_python360.sh e rode com:
bash install_python360.sh


Como alguns pacotes precisam ser instalados no Ubuntu, ele vai usar sudo. Esteja pronto para digitar a senha. No meu caso, como uso docker (docker run -rm -t -i ubuntu:16.10 /bin/bash), rodei o script como root. Se você instalar no seu usuário, ele vai chamar o sudo quando necessário. Eu gravei um pequeno vídeo do que aconteceu na minha instalação:


Windows

Depois de instalar o Python 3.6.0, instale o websockets com pip3 install websockets

Outros sistemas

Instale o Python 3.6.0 e o módulo websockets.

O novo servidor


Mudando @asyncio.coroutine para async def, o código já fica mais claro. Em uma segunda passagem, eu substitui os yield from por await. Como estamos usando Python 3.6, não custa adaptar as strings para f-strings. E para terminar a migração, configurei o log para que o código não fique cheio de prints! Ficou assim:

Antes de executar, temos que preparar um certificado SSL (no Linux).

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes


O cliente


Hoje não tem como escapar do Javascript. Fiz poucas alterações no código, a maior delas foi simplesmente cosmética e agora o texto rola para baixo automaticamente quando novas mensagens chegam.


Rodando


Imaginando que você esteja no mesmo diretório dos arquivos deste post, vamos criar um servidor web simples com python, claro:
python -m SimpleHTTPServer 8080


Deixe rodando e abra um outro terminal. Vamos executar nosso servidor:
python server.py


E finalmente, abra o browser usando localhost ou seu ip:
http://localhost:8080/cliente.html

Observação: como utilizamos um certificado auto-assinado, precisamos dar permissão ao browser de abrir a página. Como o websocket apenas usa SSL, abra uma outra janela no browser, mas na porta 8765:
https://localhost:8765

Siga o procedimento de seu browser para abriar a página. Normalmente você deve clicar em um botão dizendo que quer continuar acessando a página. Se tudo der certo, você receberá a mensagem: Invalid request. Feche a janela e recarrege o cliente em:
http://localhost:8080/cliente.html

Ele agora deve ter conectado normalmente. Abra outra janela no mesmo endereço.
Digite:
/nome X

e depois envie uma mensagem. Ela deve aparecer na outra janela. Você deve digitar /nome Nome antes de enviar mensagens. Teste com vários clientes, modifique e divirta-se.







por Nilo Menezes (noreply@blogger.com) em 21 de January de 2017 às 19:19

January 16, 2017

PythonClub

Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04

Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04

A versão mais recente do Python, a 3.7.0 alfa 1, pode agora ser baixada ou clonada do GitHub facilmente. Uma das linguagens mais fáceis de usar e aprender, o Python foi criado nos anos 90 e é elogiado por sua fácil leitura de código e necessidade de poucas linhas de código, comparada a outras linguagens. Agora mais próxima da comunidade no Github!

Depois disso os caminhos mudaram e conheci a profissão de Analista de Suporte e me ocupo disso desde então. Atualmente voltei a aprender uma linguagem, antes de mais nada, dei uma atualizada em lógica de programação, por sinal existem muitas boas apostilas e cursos gratuitos na Internet, dois caminhos muito bons.

Sobre linguagem de programação, existem várias. Neste quesito comecei a conhecer a linguagem Python e logo me apaixonei pela simplicidade, beleza e eficiência.

Depois disso tudo, você tem que instalar a linguagem em sua máquina. Por padrão, o Ubuntu 16.04 instala a versão 3.4, mas se você quiser, pode usar a versão 3.7.0a0

Obs.: Execute os comandos como root, ou usando o comando sudo no terminal.

git clone https://github.com/python/cpython
cd cpython
apt-get install build-essential libssl-dev libffi-dev python3-dev
./configure
make
make test
make install

# Se você quiser usar várias versões do Python 2.7, 3.6 e 3.7 use o comando abaixo
make altinstall

Observação: via apt instalei as dependências do python, no caso o openssl, porque o pip apresenta vários problemas com certificados na instalação dos módulos, mas, isso é para outro artigo

Depois disso é só entrar no interpretador:

python3.7

Tela do interpretador Python

Python 3.7.0a0 (default, Feb 16 2017, 18:59:44) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Referências

Para ler mais sobre a linguagem: Python - Site oficial da Linguagem Python! This is Python version 3.7.0 alpha 1 - Git da próxima versão do Python, hospedado no Github! * Python-Brasil - A comunidade Python Brasil reune grupos de usuários em todo o Brasil interessados em difundir e divulgar a linguagem de programação.

por Welton Vaz em 16 de January de 2017 às 22:37

Abrangência de Listas e Dicionários

A utilização de listas em Python é algo trivial. A facilidade provida pela linguagem aliada a simplicidade da estrutura de dados list a torna, ao lado dos dicionários dict, uma das estrutura de dados mais utilizadas em Python. Aqui neste tutorial irei compartilhar algo que aprendi trabalhando com listas e dicionário em Python, mais especificamente no que diz respeito a abrangência de listas (e dicionários).

Abrangência de listas

A abrangência de listas, ou do inglês list comprehensions, é um termo utilizado para descrever uma sintaxe compacta que o Python nos oferece para criamos uma lista baseada em outra lista. Pareceu confuso? Ok, vamos aos exemplos!

Exemplo 1

Vamos suport que temos a seguinte lista de valores:

valores = [1, 2, 3, 4, 5]

Queremos gerar uma outra lista contendo o dobro de cada um desses números, ou seja,

[2, 4, 6, 8, 10]

Inicialmente, podemos montar o seguinte código como solução:

# Recebe o nosso resultado
valores_dobro = []

for val in valores:
    valores_dobro.append(val * 2)

print(valores_dobro)

>>>
[2, 4, 6, 8, 10]

A solução acima é uma solução simples e resolve nosso problema, entretanto para algo tão simples precisamos de 4 linhas de código. Este exemplo é uma situação onde a abrangência de lista pode ser útil. Podemos compactar a criação da lista valores_dobro da seguinte maneira:

valores_dobro = [valor*2 for valor in valores]

Bacana não? O exemplo seguinte podemos incrementar mais o exemplo acima.

Exemplo 2

Vamos supor que desejamos criar uma lista onde apenas os valores pares (resto da divisão por 2 é zero) serão multiplicados por 2. Abaixo temos a nossa lista de valores:

valores = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Assim como no exemplo anterior, podemos resolver utilizando um algoritmo básico.

# Lista que recebera o nosso resultado
valores_dobro = []

for valor in valores:
    if valor % 2 == 0:
        valores_dobro.append(valor * 2)

print(valores_dobro)

>>>
[4, 8, 12, 16, 20]

Podemos também resolver o mesmo problema utilizando as funções nativas map e filter:

valores_dobro = map(lambda valor: valor * 2, filter(lambda valor: valor % 2 == 0, valores))

Muito mais complicada não é? Apesar de resolver nosso problema, expressões como a acima são difíceis de ler e até mesmo de escrever. Em casos como esse, podemos novamente compactar nosso algoritmo utilizando a abrangência de lista.

valores_dobro = [valor * 2 for valor in valores if valor % 2 == 0]

Muito mais simples, não? Vamos para o próximo exemplo.

Exemplo 3

De maneira semelhante a lista, nós também podemos aplicar a abrangência em lista e dicionários. Segue um exemplo onde temos o seguinte dicionário:

 dicionario = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}

Vamos criar um segundo dicionário contendo apenas as chaves que são consoantes, ou seja, b, c, d e f, sendo que o valor para cada uma dessas chaves deve ser o dobro do valor armazenado na respectiva chave do dicionário original. Complicado? Em outras palavras, o novo dicionário deve ficar assim:

 novo_dicionario = {'b': 4, 'c': 6, 'd': 8, 'f': 12}

Utilizando um algoritmo genérico, podemos resolver o problema da seguinte maneira:

novo_dicionario = {}

for chave, valor in dicionario:
    if chave in ['b', 'c', 'd', 'f']:
        novo_dicionario[chave] = 2 * valor

print(novo_dicionario)

>>
{'b': 4, 'c': 6, 'd': 8, 'f': 12}

Aplicando agora a abrangência, conseguimos compactar o código acima de maneira interessante:

novo_dicionario = {chave: 2 * valor for chave, valor in dicionario.items() if chave in ['b', 'c', 'd', 'f']}

Conclusão

Chegamos ao final de mais um tutorial! Sempre temos de ter em mente que tão importante quanto escrever um código que funciona, é mantê-lo (seja por você ou por outro programador). Neste ponto, a abrangência de lista (e outras estruturas de dados) nos ajudam a escrever um código claro e fácil de dar manutenção.

Até o próximo tutorial pessoal!

Publicado originalmente: Abrangencia de listas e dicionários com Python

Referências

por Michell Stuttgart em 16 de January de 2017 às 12:37

January 15, 2017

Bruno Cezar Rocha

Migrando e-commerce do Iluria para o Shopify (usando Python)

Iluria

Ilúria é uma empresa brasileira de e-commerce que fornece uma plataforma bastante interessante para quem está começando uma loja virtual e precisa de algo simples e funcional. O sistema do Ilúria é realmente simples e de fácil uso, por isso continuo recomendando essa plataforma caso a sua necessidade seja básica e seu negócio ainda estiver começando.

Porém quando você começar a ter necessidades mais específicas, personalizações no sistema de sua loja e melhor colocação nas buscas o Ilúria infelizmente deixará de te atender e surgirá a necessidade de migrar para uma plataforma mais completa.

Vantagens do Iluria

  • É uma plataforma brasileira!
    • E isso ajuda a obter suporte.
    • É legal colaborar com o crescimento de empresas nacionais!
  • O preço é bom!
    • Eles fornecem 15 dias grátis
    • Para um catálogo de 50 produtos custa R$ 9,90, 200 produtos R$ 29,90 e o preço vai aumentando de acordo com a quantidade produtos cadastrados.
  • É fácil de usar
    • A plataforma tem uma admin bastante simples e fácil de usar.

Desvantagens do Iluria

  • GOOGLE & SEO
    • Apesar de o site dizer que é otimizado para o Google, o Iluria não fornece muitas opções de fácil acesso para otimizar o SEO da loja, não tem area para customizar URLs, descriptions, tags, sitemap etc... e isso seria possível apenas programando o template.
    • As configurações padrão de SEO não são suficientes para uma boa colocação no Google.
  • Falta de relatórios analíticos
    O Iluria não oferece muitos relatórios analíticos e isso torna bastante difícil fazer re-marketing.
    • Carrinho abandonado
      O Iluria não tem controle de carrinho de compras abandonado e isso impossibilita que você lembre seu cliente sobre compras esquecidas, e está é uma das melhoras práticas para recuperar vendas.
    • Buscas
      Outro relatório interessante seria o relatório de buscas, o cliente entra na sua loja e digita na busca "azul" mas não encontra as camisetas azuis que você tem no seu catálogo e então vai para outra loja. O iluria deveria ter um relatório para te informar isso, pois dessa forma você pode melhorar a descrição e tags de seus produtos que da próxima vez a "camiseta azul" seja encontrada!
  • Não tem API
    Essa é gravíssima, injustificável, imperdoável !!!!
    Estamos em 2017 e o mundo da web gira em torno de API qualquer possibilidade de estender, criar plugins, melhorar seria através de APIs, eu enviei um e-mail para o Iluria perguntando e disseram que simplesmente não tem nenhum tipo de API e isso é injustificável para uma empresa já estabelecida como a Iluria. Sem API os problemas são:
    • Não é possível estender as funcionalidades da plataforma.
    • Não é possível programar web-hooks para disparar envios de e-mails por exemplo usando IFTTT, Zapier e outras tecnologias de automação.
    • Não é possível cadastrar produtos em massa que você já tenha em seu banco de dados ou planilha.
    • Não é possível integrar com market places como MercadoLivre e Buscapé.
    • Não ajuda nem na hora de migrar para outra plataforma como vocês verão nos códigos a seguir.
  • Seus dados não te pertencem
    Bom, pelo menos é o que parece, e isso me leva a creditar que a falta de uma API possa fazer parte de alguma estratégia do Iluria para não permitir a saída dos clientes.
    • Exportar lista de clientes e produtos é limitada. No admin até existem as opções exportar para a lista de produtos e clientes, mas as informações exportadas não são suficientes. (mostrarei em seguida)

[IMG API]

Eu ainda continuo recomendando o Ilúria para quem está começando no e-commerce é com certeza a plataforma mais acessível. Mas realmente gostaria que essa empresa abrisse os olhos para a oportunidade que eles tem em mãos e trabalhassem para oferecer mais funcionalidades para os clientes, evitando assim que abandonem a plataforma quando começarem a crescer e eu ficaria mais feliz em estar aqui falando apenas das vantagens de uma plataforma nacional de e-commerce.

Preciso migrar e agora?

Na hora que precisar migrar para outra plataforma você precisa ter certeza de que o seu histórico não será perdido, e no caso do Iluria isso é em difícil pois as opções existentes não fornecem muitos dados.

O que você precisa manter:

  • Cadastro de produtos e variantes (contendo imagens)
  • Histórico de vendas
  • Cadastro de clientes

O primeiro desafio é conseguir esses dados, no caso do projeto em que eu trabalhei na migração decidimos não migrar nem o cadastro de clientes (pois já existia um cadastro em paralelo no MailChimp) e nem o histórico de vendas (pois é possível ter esta informação no gateway de pagamento).

Portanto o que posso mostrar aqui neste post é como migrar a sua lista de produtos cadastrados e para isso utilizei a lista limitada fornecida pelo Iluria como ponto de partida e também um script em Python para pegar os dados dos produtos via crawler.

Para exportar a lista de produtos utilize o menu relatórios -> estoque de produtos conforma a imagem abaixo:

[IMG EXPORT PROD]

Você irá baixar um arquivo .csv com o seguinte formato:

Produto;Nome;Varia��o 1;Varia��o 2;Varia��o 3;Estoque;Pre�o;Pre�o de custo;Nome do fornecedor
3F553C;Madeira 147;1,40 x 1,40;;;Sob encomenda;160,00;;
3F553B;Diversos 115;1,40 x 1,40;;;Sob encomenda;160,00;;
3F553B;Diversos 115;1,40 x 2,00;;;Sob encomenda;220,00;;

Você deve estar se perguntando o porquê dos caracteres no exemplo acima?

Bom como se já não bastasse tudo o que relacionei acima o Iluria ainda surpreende com mais isso, OS DADOS ESTÃO em codificação ISO-8859-1 e mais uma vez me perguntei em que ano estamos? porquê não está em UTF-8??

E as minhas imagens?

Bom, agora que entra a parte divertida, como percebem o arquivo que exportamos acima não traz muita informação sobre o produto, não tem o texto de descrição e também não tem uma referência para a imagem do produto, portanto resolveremos isso com Python!!!

Python FTW

Puxando os dados do Iluria através de crawling

O código dessa parte é bem simples (pode melhorar) mas o que apresento aqui é o que funcionou para mim nesta migração:

primeiro vamos criar um arquivo chamado utils.py

# coding: utf-8
import csv
import shutil
import requests
from bs4 import BeautifulSoup


def get_image_and_description(produto, link):
    """Baixa a imagem do iluria e salva no diretório atual
    Pega o texto de descrição do produto e retorna
    se não encontrar retorna None.
    """

    user_agent = {'User-agent': 'Mozilla/5.0'}
    response = requests.get(link, headers = user_agent)
    if response.status_code != 200:
        return
    soup = BeautifulSoup(response.content, "html.parser")

    image_element = soup.find("img", {"id": "big-image"})
    if image_element:
        image_url = "http:{0}".format(image_element['src'])
        image_url = image_url.replace("450xN", "850xN")

        image_content = requests.get(image_url, stream=True)
        if image_content.status_code == 200:
            filename = "{0}.jpg".format(produto)
            with open(filename, "wb") as image_file:
                shutil.copyfileobj(image_content.raw, image_file)
            del image_content

    description_element = soup.find(
        "div", {"class": "product-description"}, text=True)

    if description_element:
        return description_element[0]


# HACK ALLERT!!
# A função abaixo "imita" uma classe
def IluriaDictReader(data, **kwargs):
    """Lê o csv do Iluria em ISO-8859-1"""
    csv_reader = csv.DictReader(data, **kwargs)
    for row in csv_reader:
        yield {
          key: value.decode('iso-8859-1').encode('utf8')
          for key, value in row.iteritems()
        }

A primeira função acima irá fazer o download da imagem do Iluria e também salvar a descrição do produto já que essas informações não tem no csv exportado e caso você precise de outras informação basta adicionar mais elementos ao soup.find e retornar os dados.

Até aqui com as 2 funções acima já é possível imaginar em como fazer uma migração mas agora você tem que decidir para qual plataforma migrar, vamos falar delas!

Para onde ir?

Para este projeto analisamos algumas alternativas ao Iluria e tentamos dar preferência a alternativas nacionais e vamos falar delas.

  • Box Loja Essa pareceu ser uma opção bem próxima ao Iluria, os preços são bons entre 20 e 50 por mês de acordo com a quantidade de produtos, não cobram taxas por cada venda efetuada e parece ter algumas facilidades para customização. Porém mais uma vez o que tirou essa plataforma da lista de candidatos foi a falta e API, eu vasculhei o site deles, fóruns e Google, vi até em sites de freelancers pessoas procurando quem fizesse isso via algum script robô para migrar dados de Magento para o Box loja pelo fato deles ainda não terem API. E no rodapé do site deles não tem um link bem claro escrito developers ou API, e isso foi motivo suficiente para eliminar apesar de parecer uma boa opção.

  • Loja Integrada (recomendado !!!)
    Tá aqui mais uma opção que parece ser fantástica, e como nome sugere eles fornecem uma API!!! e por isso estão de parabéns!!! Esta plataforma é muito bem falada, e realmente parece uma plataforma de e-commerce que está a frente dos concorrentes, eu gostaria muito de ter migrado este projeto para o Loja Integrada, porém aqui pesou a questão comercial, os preços do Loja Integrada ainda não são tão convidativos quanto dos concorrentes acima, e isso parece ser justo pois oferecem mais vantagens, porém neste ponto aqui o Shopify acabou ganhando

Shopify

Shopify é uma empresa Canadense que oferece uma das mais utilizadas plataformas de e-commerce do mundo (supostamente 150 mil lojas) é escrita em Ruby on Rails, mas apesar disso possui uma API bastante completa e muito bem documentada. Além disso o Shopify criou o Liquid uma linguagem de template bastante fácil e inspirada no já conhecido Jinja portante muito confortável para qualquer programador Python interagir.

enter image description here

Vantagens do Shopify

  • Confiável
    • Uma empresa do porte do Shopify mantém um suporte bastante ativo e o minimo que podemos esperar é uma plataforma estável e confiável.
  • Integrada
    • A API do Shopify é muito bem escrita e com documentação completa tornando fácil interagir tanto para importar e exportar dados, quanto para criar aplicações que estendam as funcionalidades.
  • APPs
    • Por conta da API citada acima, o Shopify oferece um market place de APPs é possível encontra ruma variedade de plugins tando gratuitos quanto comerciais para instalar na sua loja e você também pode usar a API para criar seus próprios APPs.
  • Temas!!!
    • Isso é muito importante e o Shopify parece ter feito da forma certa, pois como já mencionei a linguagem de templates é fácil de usar então isto resulta em muitos templates disponíveis gratuitamente e também empresas como Envato, Themes Monster etc oferecendo várias opções comerciais, e isto também fácil para encontrar desenvolvedores para customizar temas.
  • SEO, Google, Buscas
    • Essa é uma parte muito interessante, de maneira simples é possível customizar as opções de SEO e os resultados são muito bons, poucas horas depois de migrar já tínhamos resultados diferentes no Google. (analisados através do Google developer tools)
  • Relatórios
    • O Shopify oferece desde o plano mais básico alguns relatórios essenciais como o controle de carrinhos abandonados, buscas efetuadas, balanço de vendas etc.. E os planos mais superiores ainda oferecem relatórios customizados.
  • Smart Collections
    • Aqui está outro recurso interessante, para quem tem muitos produtos e não quer ficar organizando manualmente é possível criar regras para que os produtos sejam automaticamente colocados em determinadas categorias/menus usando condições simples como sempre que aparecer 'camiseta' no título colocar este produto na categoria 'roupas', etc...
  • Preço
    • O plano básico do Shopify custa 30 dólares (pouco mais de 100,00) e oferece um grande número de funcionalidades e ainda é possível incluir APPs para obter ainda mais recursos.
  • Biliotecas
    • O próprio Shopify mantém bibliotecas em algumas linguagens como Ruby, Java, C#, Python e PHP para interagir com a API deles :)

Desvatagens do Shopify

  • Admin em Inglês
    • Os temas de front-end podem ser traduzidos para qualquer lingua, mas o admin apenas em inglês e isso dificulta bastante a adoção mesmo para quem fala a lingua inglesa pois alguns termos como "fulfillment" não são de fácil tradução.
  • Complexidade do Admin
    • Este problema só ocorre na primeira semana de uso, em pouco tempo você já se acostuma com a UI do admin, porém nas primeiras horas navegando você irá soltar muitos "What The Fuck???"
  • Compatibilidade com a realidade brasileira
    • O Shopify já atende bem tudo o que uma loja brasileira precisa, porém é bem claro que estão preparados para um estilo diferente de comércio, algumas coisas como taxas, estoque etc são feitas de um modo que não é usual no Brasil mas isso acredito que seja mais um problema de adaptação pois talvez nossos comércios que precisem se adaptar a um esquema mais organizado, e isso exige tempo e paciência.
  • Meios de pagamento
    • É possível configurar Paypal, PagSeguro, Mercadopago, Moip, Bitcoins entre outros. Eles tem o sistema preparado para trabalhar bem com todas essas plataformas, porém você só pode escolher Paypal + 1 para ser ativado em sua loja simultaneamente, ou seja, Paypal + Pagseguro, ou Paypal + Mercado Pago. Não é possível dar opção de seu cliente escolher qual gateway deseja usar.
  • Correios só através de app
    • Até existe um cálculo de correio integrado, mas é por faixa de CEP e você precisa configurar os preços manualmente, para ter um frete automático você precisa usar um APP adicional, é muito fácil de instalar, basta clicar em um botão! mas você tem que pagar + 5 dólares por mês para usar.

Mesmo com as desvantagens listadas acima o Shopify pareceu uma boa escolha, e a empresa está ativamente respondendo questões de brasileiros no seu fórum indicando que logo irão implementar mais facilidades e resolver essas limitações.

Decidimos migrar para o Shopify!!!

Interagindo com a API do Shopify via Python

Apesar do Shopify manter uma biblioteca Python para interagir com a API deles, eu analisei e achei que a solução mantida por eles não é muito Pythonica então continuei procurando.

Encontrei o projeto Python-Shopify que ainda não estava totalmente funcional e então fiz um fork e comecei a contribuir, arrumei alguns bugs e fiz o release para o PyPI. portanto agora é possível usar com pip install python-shopify.

Portanto em nosso projeto agora é a hora de criar o código que vai popular os produtos no banco de dados do Shopify usando aqueles dados que extraímos no código que mostrei acima.

Rode pip install python-shopify slugify tqdm e então no arquivo api.py

import os
from slugify import slugify
from shopify.products import (
    ProductsApiWrapper, Product, Image, Variant, Option
)


# FILL THE DATA below with data generated in Shopify-> admin -> apps
api_key = ''  
password = '' 
store_name = 'sua-loja'

paw = ProductsApiWrapper(api_key, password, store_name)

# Get a list of existing products, limited to 250 :(
existing = [item.title for item in paw.list(limit=250)]


def create_product(items):
    """Items is a list of dictionaries representing each product variant
    of the same product with the same ID and other data
    keys: ['description', 'price', 'name', 'link', 'size', 'stock']
    items = [
        # first variant holds full data and is default
        {'name': 'Awesome t-shirt',
         'code': '123456',
         'description': '<html>',
         'size': 'P',
         'price': '22.5',
         'stock': 2},
        # Other variants
        {'size': 'M',
         'price': '25.5',
         'stock': 2},
        {'size': 'G',
         'price': '29.5',
         'stock': 0},
    ]
    """

    # The first item should be the complete item holding all the fields
    # other items can have only the variants
    data = items[0]

    # Iluria gives us ISO-8859-1 :(
    name = data['name'].decode('utf-8')


    if name in existing or paw.list(title=name):
        # skip existing
        print "Already registered, skipping..."
        # or perform an update!!!
        return

    product = Product(
        title=data['name'],
        body_html=data['description'],
    )

    # There should be a 123456.jpg file in the same folder
    # alternatively you can use a URL provided in data
    image_filename = "{0}.jpg".format(data['code'])
    if os.path.exists(image_filename):
        image = Image()
        image.attach(image_filename)
        product.add_image(image)
    elif data.get('image_url'):
        product.add_image(Image(src=data['image_url']))

    # using the first word in title as tag
    # Product "T-shirt Blue 09" got tag "t-shirt"
    tag = data['name'].split()[0]
    tag = u''.join(i for i in tag if not i.isdigit())

    product.add_tags(tag.strip().lower())

    # You can add only 3 options
    # at positions 1, 2 and 3
    # you should add options before adding its variants
    product.add_option(
      Option(
        name="Size",
        position=1,
      )
    )

    for item in items:
        product.add_variant(
            Variant(
                option1=item['size'],
                # option2=data['size'],
                # option3=data['size'],
                title="Size",
                price=item['price'],
                # SKU should be different for each variant
                sku=data["code"] + slugify(item['size']), 
                position=1,
                inventory_policy="continue",
                fulfillment_service="manual",
                inventory_management="shopify",
                inventory_quantity=int(item['stock']),
                taxable=False,
                weight=300,
                weight_unit="g", # g, kg
                requires_shipping=True
            )
        )

    try:
        product = paw.create(product)
    except Exception as e:
        # do a proper logging here please!!!
        print e
        print product
        print items

    return product

Eu inclui o exemplo acima no repositório do Python-Shopify

Migrando do Iluria para o Shopify!!!

Agora precisamos juntar nossos 2 arquivos utils.py e api.py em um script e ai rodar a migração dos produtos.

import os
from collections import defaultdict
from tqdm import tqdm

from api import create_product
from utils import get_image_and_description, IluriaDictReader

BASE_URL = "http://www.sua_loja_iluria.com.br/pd-"
reader = IluriaDictReader(open('iluria_produtos_estoque.csv'), delimiter=";")
produtos = defaultdict(list)

for item in reader:
    size = item['Varia\xe7\xe3o 1']
    produtos[item["Produto"]].append(
       {
        "link": "{base}{slug}.html".format(
            base=BASE_URL, slug=item['Produto']
        ),
        "name": item["Nome"],
        "size": size,
        "price": item['Pre\xe7o'].replace(",", "."),
        "stock": item['Estoque'],
        "code": item['Produto']
       }
    )

for produto, items in tqdm(produtos.items()):
    data = items[0]  # cada item é uma lista com variações

    # pegamos a descrição e já fazemos o download da imagem
    # idealmente teriamos 2 funções, mas estamos só hackeando!!! :)
    data['description'] = get_image_and_description(
        data['code'], data['link']
    )

    if not os.path.exists('{0}.jpg'.format(produto)):
        # sem imagem sem cadastro!!!
        continue

    if not data['name']:
        # name é obrigatório
        continue

    # criamos o produto na API do Shopify e success!!!
    create_product(items)

Após o término do script você terá seus produtos cadastrados no Shopify e então os próximos passos será escolher e customizar seu tema (ou criar um próprio) e ai configurar sua loja, dominios, frete etc..

Apps recomendados:

Conclusão

Iluria uma empresa que tem uma boa plataforma mas que está perdendo a oportunidade de se tornar a maior plataforma de e-commerce brasileira por simplesmente não investir em evolução tecnológica de sua plataforma.

Loja Integrada aparentemente a melhor opção para quem quer se manter em uma plataforma brasileira, não tenho mais informações pois não cheguei realmente a utilizar, mas eles poderiam melhorar os preços de entrada, assim iriam atrair as lojas que inevitavelmente irão sair do Iluria

Shopify uma ótima opção, com muita coisa a melhorar para o público brasileiro mas mesmo assim com um pouco de dedicação e leitura das documentações é possível criar uma loja 100% funcional em poucas horas!!!

Para referencia: A loja que migramos no projeto citado é a https://fundosemtecido.com.br/ que comercializa fundos fotográficos para fotógrafos e conseguimos efetuar a migração desde a exportação dos dados, criação de tema personalizado, configurações de admin até colocar no ar em apenas 2 dias.

Links:

Python-Shopify

por Bruno Rocha em 15 de January de 2017 às 21:15

December 28, 2016

Gabbleblotchits

Minhashing all the things (part 1): microbial genomes

With the MinHash craze currently going on in the lab, we started discussing how to calculate signatures efficiently, how to index them for search and also how to distribute them. As a proof of concept I started implementing a system to read public data available on the Sequence Read Archive, as well as a variation of the Sequence Bloom Tree using Minhashes as leaves/datasets instead of the whole k-mer set (as Bloom Filters).

Since this is a PoC, I also wanted to explore some solutions that allow maintaining the least amount of explicit servers: I'm OK with offloading a queue system to Amazon SQS instead of maintaining a server running RabbitMQ, for example. Even with all the DevOps movement you still can't ignore the Ops part, and if you have a team to run your infrastructure, good for you! But I'm a grad student and the last thing I want to be doing is babysitting servers =]

Going serverless: AWS Lambda

The first plan was to use AWS Lambda to calculate signatures. Lambda is a service that exposes functions, and it manages all the runtime details (server provisioning and so on), while charging by the time and memory it takes to run the function. Despite all the promises, it is a bit annoying to balance everything to make an useful Lambda, so I used the Gordon framework to structure it. I was pretty happy with it, until I added our MinHash package and, since it is a C++ extension, needed to compile and send the resulting package to Lambda. I was using my local machine for that, but Lambda packaging is pretty much 'put all the Python files in one directory, compress and upload it to S3', which of course didn't work because I don't have the same library versions that Amazon Linux runs. I managed to hack a fix, but it would be wonderful if Amazon adopted wheels and stayed more in line with the Python Package Authority solutions (and hey, binary wheels even work on Linux now!).

Anyway, after I deployed the Lambda function and tried to run it... I fairly quickly realized that 5 minutes is far too short to calculate a signature. This is not a CPU-bound problem, it's just that we are downloading the data and network I/O is the bottleneck. I think Lambda will still be a good solution together with API Gateway for triggering calculations and providing other useful services despite the drawbacks, but at this point I started looking for alternative architectures.

Back to the comfort zone: Snakemake

Focusing on computing signatures first and thinking about other issues later, I wrote a quick Snakemake rules file and started calculating signatures for all the transcriptomic datasets I could find on the SRA. Totaling 671 TB, it was way over my storage capacity, but since both the SRA Toolkit and sourmash have streaming modes, I piped the output of the first as the input for the second and... voila! We have a duct-taped but working system. Again, the issue becomes network bottlenecks: the SRA seems to limit each IP to ~100 Mbps, it would take 621 days to calculate everything. Classes were happening during these development, so I just considered it good enough and started running it in a 32-core server hosted at Rackspace to at least have some signatures to play with.

Offloading computation: Celery + Amazon SQS

With classes over, we changed directions a bit: instead of going through the transcriptomic dataset, we decided to focus on microbial genomes, especially all those unassembled ones on SRA. (We didn't forget the transcriptomic dataset, but microbial genomes are small-ish, more manageable and we already have the microbial SBTs to search against). There are 412k SRA IDs matching the new search, totalling 28 TB of data. We have storage to save it, but since we want a scalable solution (something that would work with the 8 PB of data in the SRA, for example), I avoided downloading all the data beforehand and kept doing it in a streaming way.

I started to redesign the Snakemake solution: first thing was to move the body of the rule to a Celery task and use Snakemake to control what tasks to run and get the results, but send the computation to a (local or remote) Celery worker. I checked other work queue solutions, but they were either too simple or required running specialized servers. (and thanks to Gabriel Marcondes for enlightening me about how to best use Celery!). With Celery I managed to use Amazon SQS as a broker (the queue of tasks to be executed, in Celery parlance), and celery-s3 as the results backend. While not an official part of Celery, using S3 to keep results allowed to avoid deploying another service (usually Celery uses redis or RabbitMQ for result backend). I didn't configure it properly tho, and ended up racking up \$200 in charges because I was querying S3 too much, but my advisor thought it was funny and mocked me on Twitter (I don't mind, he is the one paying the bill =P). For initial tests I just ran the workers locally on the 32-core server, but... What if the worker was easy to deploy, and other people wanted to run additional workers?

Docker workers

I wrote a Dockerfile with all the dependencies, and made it available on Docker hub. I still need to provide credentials to access SQS and S3, but now I can deploy workers anywhere, even... on the Google Cloud Platform. They have a free trial with \$300 in credits, so I used the Container Engine to deploy a Kubernetes cluster and run workers under a Replication Controller.

Just to keep track: we are posting Celery tasks from a Rackspace server to Amazon SQS, running workers inside Docker managed by Kubernetes on GCP, putting results on Amazon S3 and finally reading the results on Rackspace and then posting it to IPFS. IPFS is the Interplanetary File System, a decentralized solution to share data. But more about this later!

HPCC workers

Even with Docker workers running on GCP and the Rackspace server, it was progressing slowly and, while it wouldn't be terribly expensive to spin up more nodes on GCP, I decided to go use the resources we already have: the MSU HPCC. I couldn't run Docker containers there (HPC is wary of Docker, but we are trying to change that!), so I used Conda to create a clean environment and used the requirements file (coupled with some PATH magic) to replicate what I have inside the Docker container. The Dockerfile was very useful, because I mostly ran the same commands to recreate the environment. Finally, I wrote a submission script to start a job array with 40 jobs, and after a bit of tuning I decided to use 12 Celery workers for each job, totalling 480 workers.

This solution still requires a bit of babysitting, especially when I was tuning how many workers to run per job, but it achieved around 1600 signatures per hour, leading to about 10 days to calculate for all 412k datasets. Instead of downloading the whole dataset, we are reading the first million reads and using our streaming error trimming solution to calculate the signatures (and also to test if it is the best solution for this case).

Clever algorithms are better than brute force?

While things were progressing, Titus was using the Sequence Bloom Tree + Minhash code to categorize the new datasets into the 50k genomes in the [RefSeq] database, but 99\% of the signatures didn't match anything. After assembling a dataset that didn't match, he found out it did match something, so... The current approach is not so good.

(UPDATE: it was a bug in the search, so this way of calculating signatures probably also work. Anyway, the next approach is faster and more reasonable, so yay bug!)

Yesterday he came up with a new way to filter solid k-mers instead of doing error trimming (and named it... syrah? Oh, SyRAh... So many puns in this lab). I created a new Celery task and refactored the Snakemake rule, and started running it again... And wow is it faster! It is currently doing around 4200 signatures per hour, and it will end in less than five days. The syrah approach probably works for the vast majority of the SRA, but metagenomes and metatranscriptomes will probably fail because the minority members of the population will not be represented. But hey, we have people in the lab working on that too =]

Future

The solution works, but several improvements can be made. First, I use Snakemake at both ends, both to keep track of the work done and get the workers results. I can make the workers a bit smarter and post the results to a S3 bucket, and so I only need to use Snakemake to track what work needs to be done and post tasks to the queue. This removes the need for celery-s3 and querying S3 all the time, and opens the path to use Lambda again to trigger updates to IPFS.

I'm insisting on using IPFS to make the data available because... Well, it is super cool! I always wanted to have a system like bittorrent to distribute data, but IPFS builds up on top of other very good ideas from bitcoin (bitswap), and git (the DAG representation) to make a resilient system and, even more important, something that can be used in a scientific context to both increase bandwidth for important resources (like, well, the SRA) and to make sure data can stay around if the centralized solution goes away. The Cancer Gene Trust project is already using it, and I do hope more projects show up and adopt IPFS as a first-class dependency. And, even crazier, we can actually use IPFS to store our SBT implementation, but more about this in part 2!

por luizirber em 28 de December de 2016 às 14:00

December 27, 2016

JungleCoders

Consultas via Telegram

Seria legal se profissionais de informática dessem consultas como médicos ou advogados, mas algo nos impede de cobrar por tudo e esse desejo ou intenção de compartilhar ideias nos consome.

Eu participo de vários grupos de Telegram, principalmente sobre Python, um deles é o PyCoding e o outro é o pybr. Normalmente eu leio os grupos quando estou usando meu celular, então nem sempre é possível ajudar com as dúvidas, mas vou tentar separar um pouco de tempo para explorar algumas ideias aqui e lá.

Hoje está tão fácil aprender qualquer coisa que tenho notado uma ansiedade cada vez maior de quem começa a programar de aprender tudo. Em um só mês, algumas pessoas querem aprender Python, SciPi, TensorFlow, Android e o que mais der. Um mês é pouco tempo. Pode-se aprender a programar em períodos relativamente pequenos, mas leva tempo para se acostumar com as novas ideias, linguagens e bibliotecas. O Peter Norvig comentou sobre essa ansiedade no Learn Programming in Ten Years.

Cálculo de médias


Vamos ao interesse do post, a tal consulta.

O colega Wesley enviou dois programas, vou começar pelo mais simples. Primeiro vamos desconsiderar os palavrões, nosso colega é jovem.

Uma coisa que gostei muito foi a primeira linha de mensagens. Poucos se preocupam em dizer o que faz o programa, isso é legal! Eu faria apenas uma pequena modificação para que a linha não fosse tão grande.

Como eu cresci nos anos 80, sem letras minúsculas e acentos, é questão de honra corrigir as mensagens.

Nas linhas 7 a 11, os valores das variávies m1, n1, n2, n3 e n4 são solicitados. Como a função input retorna strings, veja que no resto do programa a função float foi utilizada para converter estes valores. Neste caso, o valor convertido deveria ser armazenado diretamente na variável.

Desta forma, simplificamos a linha 12 de forma a facilmente perceber um erro de prioridade de operações. Quando fazemos o cálculo de n1 + n2 + n3 + n4 / 4, sem utilizar parênteses, as operações são realizados por ordem de prioridade, como na matemática. Assim, n4/4 é somado a n1, n2 e n3. Para calcular a média, precisamos de parênteses: (n1 + n2 + n3 + n4) / 4. Agora, a soma das notas é calculada e depois dividida por quatro, como queríamos.

Entre as linhas 15 e 19 acredito que tenha sido apenas um teste. Vou remover para não atrapalhar o entendimento do programa final.

As linhas de 24 a 35 imprimem vários pontos, vou apenas simplificar.

Para terminar, pequenas modificações para usar as f strings do Python 3.6.


Programa com tkinter

O outro programa é uma interface gráfica, usando tkinter.

Como os fontes foram postados no Telegram, muito se perde. De cara há um problema com o import da linha 2. Eu parabenizo o Wesley pela coragem de usar o tkinter. É umas das partes do Python que menos gosto, mas que funciona.


Deve-se evitar os import * no Python, isso polui o namespace e causa problemas chatos de resolver. No caso do tkinter, é um caso a se pensar, mas nunca misturar o * com os imports de classes e funções individuais.

Uma coisa que salta aos olhos, não, não falo do fundo caladryl, mas da repetição da cor em várias partes do código. Vamos criar uma constante para cor de fundo, melhor, vamos retirar as cores e deixar as cores padrão.

Um outro problema é a validação de valores, acrescentei uma função float_ou_zero que retorna zero caso o valor digitado não possa ser convertido para float.

Usar tkinter sem classes é um tanto confuso, eu particularmente não gosto de ter funções com variáveis globais e de ter definições de funções e variáveis misturadas, mas isso é assunto para outro post.


Vejamos como ficou!




Convertendo ints

Outro post interessante foi o de como converter vários ints de uma só vez. O problema inicial era calcular um valor do tipo hh:mm:ss em total de segundos.


O que me chamou atenção foi uma das soluções:

Correta, porém, achei que o foco da solução não era mais o problema inicial, mas fazer em menos linhas. De repente, passa o medo de "Perlizar" o Python.
O problema em si, exige validação dos dados. Este é um detalhe importante que é fácil de ser esquecido. Então, ao invés de fazer com menos linhas, vamos adicionar o mínimo de validação.
Esta solução utiliza o módulo datetime do Python e o tipo time para validar as horas entre 0 e 23, minutos entre 0 e 60 e o mesmo para segundos. Se o usuário entrar um valor errado, terá que redigitar após receber uma mensagem de erro. Embora eu tenha usado a expansão de listas duas vezes em uma só linha (Perlização?), acho que o código ficou relativamente bom.

São detalhes, mas que fazem a diferença em programas maiores. Nem sempre escrever em menos linhas é o mais correto ou deveria ser o foco principal da solução de um problema.

Ainda sobra margem para uma outra solução, onde criamos uma função para converter horas, minutos e segundos para total em segundos.

Além da validação (ainda que mínima), ganhamos a flexibilidade de digitar valores como 10, 10:20 ou 10:20:30. O programa que fizemos pode ser importado por outros programas e suas funções reutilizadas, sem perder a funcionalidade inicial se usado como programa principal.

por Nilo Menezes (noreply@blogger.com) em 27 de December de 2016 às 16:24

December 12, 2016

Aprenda Python

TDD serve como documentação

No artigo [Por que TDD?](/2016/10/por-que-tdd.html) eu escrevi sobre alguns benefícios de usar TDD, mas foi um texto bem conceitual, sem exemplos. Nesse aqui eu quero mostrar um dos benefícios de testes automatizados na prática: servir como documentação técnica funcional. Leia com atenção o trecho de código abaixo: um_mes_atras = datetime.date.today() - timedelta(days=30) URL = (

por Vinicius Assef (noreply@blogger.com) em 12 de December de 2016 às 12:38

December 10, 2016

Thiago Avelino

SENAI abre espaço para compartilhamento da minha história de empreendimento na área Adulto (+18)

O sonho do dono de Startup é ter uma empresa comprada, o que você realmente pensa em fazer com esse dinheiro?

Para quem caiu aqui sem saber quem é o Avelino, sou ex-dono do sambaporno.com (agregador de conteúdo Adulto, a inteligência é categorização) onde tínhamos média 55 milhões de pageviews/dia, para entender da evolução tecnológica do Samba veja o vídeo abaixo.

https://medium.com/media/414bd0d1a1cf12ec8a9db16bb774f427/href

Hoje (dia 10/12/2016) tive o prazer de bater um papo com os alunos (futuro empreendedores) do SENAI, foi uma palestra onde eu contei minha história no mundo de empreendedorismo indo da concepção do projeto até venda para uma grade empresa de fora do Brasil, abrindo algumas tomadas de decisão onde precisava fazer o site dar dinheiro para pagar os gastos. O Samba nasceu como um site pessoal e isso me gerou alguns problemas, pois não encarava como um negócio e sim como um brinquedo pessoal (a bola é minha, se eu jogar ninguém joga).

Devemos variar nossos investimento, Filmow entrou na minha carteira de investimento por esse motivo

Depois de anos fazendo o Samba acontece eu resolvi abrir meu leque de negócios e entrei de sócio no Filmow (rede social para cinéfilo), foi uma ótima experiência em lidar com usuários conectado onde a página precisa ser customizada para cada cliente, estamos em processo de atualização do sistema atual (escrito em Python e banco de dados PostgreSQL) para usar menos recurso de servidor, usando processamento concorrente.

Falei como é o meu processo de selecionar a tecnologia que vou usar para fazer determinado projeto, se meu projeto (startup) está nascendo agora eu vejo o que é mais rápido para colocar no seu, sem pensa em escalabilidade e/ou qual quer tipo de tecnologia muito avançada. Empreender é ter risco então precisamos minimizar ao máximo nosso risco.

Para finalizar falei um pouco para usarmos projetos open source para acelerar o processo de colocar o MVP em produção, mas precisamos sempre está atento com a licença do software que estamos usando, se esse software X tem licença restritiva em colocar esse código comercialmente isso pode ser um problema na hora de colocar cliente usando seu software.

Após a palestra, gravamos um vídeo para página do Facebook do SENAI onde falei aonde vejo que devemos focar esforços nos próximos anos (o que vejo do futuro):

https://medium.com/media/b27b2832b515b62a07b5553a59b689e0/href

por Avelino em 10 de December de 2016 às 19:36

December 05, 2016

Aprenda Python

Como mudar itens de uma tupla

Tuplas são objetos imutáveis. Se dermos um `dir(tuple)` no IDLE do Pyton, veremos que só existem 2 métodos considerados públicos -- `count()` e `index()` -- e eles não servem para modificá-la. Os exemplos desse artigo serão quase todos com os dados abaixo: >>> nome = 'Marta' >>> notas = {'matematica': 8.5} >>> brinquedos = ['mesinha', 'patins'] >>> mesada = 10.00 >>> t =

por Vinicius Assef (noreply@blogger.com) em 05 de December de 2016 às 11:21

November 27, 2016

PythonClub

Debugging - logging

Achei algo interessante no livro que estou lendo (Automatize tarefas maçantes com Python) e resolvi compartilhar.

Trata-se do Logging, que ajuda no debug do programa.

Vejam o exemplo nesse programa, com falha:

import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')


logging.debug('Start of program')

def factorial(n):
    logging.debug('Start of factorial(%s%%)' % (n))
    total = 1
    for i in range(n+1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
    logging.debug('End of factorial(%s%%)' % (n))
    return total

print(factorial(5))
logging.debug('End of program')

O programa retorna:

 2016-11-15 16:17:30,339 - DEBUG - Start of program
 2016-11-15 16:17:30,340 - DEBUG - Start of factorial(5%)
 2016-11-15 16:17:30,340 - DEBUG - i is 0, total is 0
 2016-11-15 16:17:30,340 - DEBUG - i is 1, total is 0
 2016-11-15 16:17:30,340 - DEBUG - i is 2, total is 0
 2016-11-15 16:17:30,340 - DEBUG - i is 3, total is 0
 2016-11-15 16:17:30,340 - DEBUG - i is 4, total is 0
 2016-11-15 16:17:30,340 - DEBUG - i is 5, total is 0
 2016-11-15 16:17:30,340 - DEBUG - End of factorial(5%)
 2016-11-15 16:17:30,340 - DEBUG - End of program
0

Dessa forma, podemos ver o passo a passo que o programa está realizando e identificar onde está o erro. No caso, vemos que para corrigir o problema, devemos alterar o for i in range(n+1): para for i in range(1, n+1):. Quando o desenvolvedor não quiser mais visualizar as mensagens de logging, basta chamar logging.disable(logging.CRITICAL) logo embaixo do import logging. Essa função faz com que não seja necessário alterar o programa removendo todas as chamadas de logging manualmente.

Também é possível gravar as mensagens de log num arquivo, ao invés de mostrá-las na tela. A função aceita o argumento filename.

import logging
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')

Lado negativo do uso dessa função: a leitura do código fica difícil, por causa desse monte de logging.debug no meio do código. Para evitar isso, pode-se usar um decorator.

por Bruno Santana em 27 de November de 2016 às 19:48

Bruno Cezar Rocha

AsyncIO - O futuro do Python mudou completamente!

Tradução do artigo original escrito por Yeray Diaz para o hackernoon: AsyncIO for the working Python developer

Eu me lembro do exato momento em que eu pensei, "Uau, isso está lento, aposto que se eu pudesse paralelizar essas chamadas isso voaria!" e então, 3 dias após, eu olhei para o meu código e não pude reconhece-lo, havia se transformado em um misturado de chamadas para threading e funções da biblioteca processing.

Então encontrei o asyncio, e tudo mudou!


Se você não conhece, asyncio é o novo módulo de concorrência introduzido no Python 3.4. É projetado para usar coroutines e futures para simplificar a programação assíncrona e tornar o código tão legível quanto o código síncrono simplesmente por não haver callbacks.

Eu também me lembro que enquanto eu estava naquela busca pelo paralelismo inúmeras opções estavam disponíveis, mas uma se destacou. Era rápida, fácil de aprender e muito bem escrita: A excelente biblioteca gevent. Eu cheguei até ela lendo o encantador tutorial mão na massa: Gevent for the Working Python Developer, escrito por uma sensacional comunidade de usuários, uma ótima introdução não apenas ao gevent mas ao conceito de concorrência em geral, e você também deveria dar uma lida.

Eu gostei tanto do tutorial que decidi usa-lo como template para escrever sobre o AsyncIO.

Aviso Rápido: Isto não é um artigo sobre gevent X asyncio, O Nathan Road escreveu a respeito das diferenças e similaridades entre esses 2 se você estiver interessado.

Uma nota a respeito do código neste tutorial, você deve ter lido que no Python 3.5 uma nova sintaxe foi introduzida, especialmente para coroutines, eu estou intencionalmente não utilizando esta nova sintaxe neste texto pois desta forma acredito que fica mais fácil para assimilar as coroutinas com generators. Mas você pode encontrar versões dos exemplos usando esta nova sintaxe no github.

Eu sei que você já deve estar ansioso mas antes de mergulharmos eu gostaria de primeiramente falar rapidamente sobre alguns conceitos que talvez não lhe sejam familiares.

Threads, loops, coroutines and futures

Threads são uma ferramenta comum e a maioria dos desenvolvedores já ouviu falar ou já usou. Entretanto o asyncio usa de estruturas um pouco diferentes: event loops, coroutines e futures.

  • Um event loop gerencia e distribui a execução de diferentes tarefas. Ele mantém um registro de tarefas (coroutines) e distribui o fluxo de execução entre elas.
  • As coroutines são geradores Python (generators), que quando há a ocorrência do yield libera o controle do fluxo de volta ao event loop. Uma coroutine precisa estar programada para ser executada usando o event loop, para fazer isso criamos uma tarefa do tipo future.
  • E um future é um objeto que representa o resultado de uma tarefa que pode, ou não, ter sido executada. Este resultado pode ser uma Exception.

Entendeu? simples né? vamos mergulhar neste conceito!

Execução síncrona e Execução assíncrona

Em Concorrência não é paralelismo, é melhor! o Rob Pike falou uma coisa que fez um click na minha cabeça: Dividir tarefas em sub-tarefas concorrentes já é o suficiente para permitir o paralelismo. Mas é o fato de programar/agendar a execução dessas sub-tarefas que realmente cria o paralelismo.

O ASyncIO faz exatamente isso, você pode estruturar o seu código em sub-tarefas definidas como coroutines e isso te permite programar a execução da maneira que desejar, incluindo a forma simultânea. As Corountines contém pontos de vazão demarcados com a palavra yield onde definimos onde uma troca de contexto poderia ocorrer caso existam outras tarefas pendentes, mas que não irá ocorrer caso não existam outras tarefas.

Nota do tradutor: Em uma loja de doces há um funcionário empacotando balas, ao finalizar cada pacote ele o lacra e coloca na vitrine (YIELD), então ele dá uma olhada no balcão para ver se tem algum cliente para ser atendido, se tiver um cliente, então ele para de empacotar balas atende o pedido do cliente (troca de contexto). E só depois de terminar de > atender o cliente ele então volta a empacotar as balas, caso não tenha cliente a ser atendido ele simplesmente continua o trabalho de empacotamento. Podemos dizer que é um funcionário fazendo duas tarefas __. (responda nos comentários se é paralelamente ou concorrentemente)

Uma troca de contexto no asyncio representa o event loop passando o fluxo
de controle da coroutine em execução para a próxima na fila de execução, ou seja, (Yielding).

Veja um exemplo básico:

import asyncio

@asyncio.coroutine
def empacotar_bala():
    print("Empacotando balas...")

    # parada para verificar se tem cliente no balcão
    yield from asyncio.sleep(0)

    # troca de contexto
    print("Explicitamente voltando a empacotar balas")


@asyncio.coroutine
def atender_balcao():
    print("Explicitamente verificando se tem cliente no balcão...")

    yield from asyncio.sleep(0)

    print("Voltando a empacotar as balas")


ioloop = asyncio.get_event_loop()  # Event Loop

tasks = [ioloop.create_task(empacotar_bala()),
         ioloop.create_task(atender_balcao())]

wait_tasks = asyncio.wait(tasks)

ioloop.run_until_complete(wait_tasks)

ioloop.close()

Execute:

$ python3 async1.py
Empacotando balas...
Explicitamente verificando se tem cliente no balcão...
Explicitamente voltando a empacotar balas
Voltando a empacotar as balas
  • Primeiramente nós declaramos duas tarefas simples com a intenção de serem executadas de maneira não bloqueante pois usamos a função sleep do asyncio.
  • Coroutines só podem ser chamadas por outras coroutines ou podem ser agrupadas em uma task para então serem enfileiradas, nós usamos a função create_task para fazer isso.
  • Então criamos lista contendo as 2 tasks e nós a combinamos em uma wait que é uma task que irá aguardar até que todas as tarefas
    enfileiradas terminem.
  • E finalmente nós programamos a wait para executar usando o event loop usando a função run_until_complete.

Ao usar yield from na coroutine empacotar_bala nós declaramos que a coroutine pode naquele momento passar o controle do fluxo de execução de volta para o event loop, neste caso o sleep ao terminar (note o sleep(0)) irá devolver o controle ao event loop que irá mudar de contexto, passando o controle de fluxo para a próxima coroutine agendada para execução: atender_balcao

Nota: O tradutor alterou os nomes das funções dos exemplos do artigo original para dar um significado mais fácil de ser interpretado em português mas mantendo a semântica e fluxo de execução dos códigos, todavia os originais estão no github.

Vamos agora simular duas tarefas bloqueantes gr1 e gr2, considere que há dois requests para serviços externos. Enquanto elas executam, uma terceira tarefa pode ser executada assíncronamente, como no seguinte exemplo:

import time
import asyncio

start = time.time()

def tic():
  return 'at %1.1f segundos' % (time.time() - start)


@asyncio.coroutine
def gr1():
  # Demora a ser executada, mas não queremos esperar
  print('gr1 iniciou a execução: {}'.format(tic()))
  yield from asyncio.sleep(2)
  print('gr1 terminou a execução: {}'.format(tic()))


@asyncio.coroutine
def gr2():
  # Demora a ser executada, mas não queremos esperar
  print('gr2 iniciou a execução: {}'.format(tic()))
  yield from asyncio.sleep(2)
  print('gr2 terminou a execução: {}'.format(tic()))


@asyncio.coroutine
def gr3():
  print('Executando enquanto as outras estão bloqueadas: {}'.format(tic()))
  yield from asyncio.sleep(5)
  print('Pronto!')

ioloop = asyncio.get_event_loop()
tasks = [
    ioloop.create_task(gr1()),
    ioloop.create_task(gr2()),
    ioloop.create_task(gr3())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

Execute:

$ python3 async2.py 
gr1 iniciou a execução: at 0.0 segundos
gr2 iniciou a execução: at 0.0 segundos
Executando enquanto as outras estão bloqueadas: at 0.0 segundos
gr1 terminou a execução: at 2.0 segundos
gr2 terminou a execução: at 2.0 segundos
Pronto!

Perceba que na forma que o I/O loop faz o gerenciamento e programa a execução permite que o seu código, rodando em single thread possa operar de forma concorrente. Enquanto duas tarefas estavam bloqueadas uma terceira pode tomar o controle do fluxo de execução e ser executada de maneira assíncrona.

Ordem de execução

No mundo síncrono estamos acostumados a pensar de maneira linear. Se nós tivermos uma lista de tarefas que consomem diferente quantidade de tempo elas serão executadas na ordem em que foram chamadas.

Porém, quando usamos concorrência nós precisamos estar cientes de que as tarefas terminam em tempos que diferem da ordem em que foram enfileiradas.

import random
from time import sleep
import asyncio


def task(pid):
    """Uma tarefa não deterministica"""
    sleep(random.randint(0, 2) * 0.001)
    print('Task %s terminada' % pid)


@asyncio.coroutine
def task_coro(pid):
    """Uma tarefa deterministica"""
    yield from asyncio.sleep(random.randint(0, 2) * 0.001)
    print('Task %s terminada' % pid)


def synchronous():
    for i in range(1, 10):
        task(i)


@asyncio.coroutine
def asynchronous():
    tasks = [asyncio.async(task_coro(i)) for i in range(1, 10)]
    yield from asyncio.wait(tasks)


print('Síncronamente:')
synchronous()

ioloop = asyncio.get_event_loop()
print('Assíncronamente:')
ioloop.run_until_complete(asynchronous())

ioloop.close()

Execute:

$ python3 async3.py 
Síncronamente:
Task 1 terminada
Task 2 terminada
Task 3 terminada
Task 4 terminada
Task 5 terminada
Task 6 terminada
Task 7 terminada
Task 8 terminada
Task 9 terminada

Assíncronamente:
Task 2 terminada
Task 4 terminada
Task 8 terminada
Task 5 terminada
Task 6 terminada
Task 7 terminada
Task 9 terminada
Task 1 terminada
Task 3 terminada

A saida será com certeza variada, pois cada task espera por uma quantidade randômica de tempo, mas repare que a ordem dos resultados é completamente diferente, mesmo tendo enfileirado em uma lista de tarefas na mesma ordem usando o mesmo range.

Outro detalhe é que tivemos que uma versão em coroutine da nossa simples função. É importante entender que o asyncio não faz com que as coisas se transformarem magicamente em não bloqueantes.

O AsyncIO está por enquanto sozinho na biblioteca padrão do Python 3 enquanto todos outros módulos oferecem apenas funcionalidades bloqueantes.

Você pode usar o módulo concurrent.futures para agrupar tarefas bloqueantes em uma thread ou um processo e então retornar um Future que o asyncio pode utilizar. Os mesmos exemplos utilizando threads podem ser encontrados no github

Esta é provavelmente a maior desvantagem ao usar asyncio neste momento, porém existe uma série de bibliotecas para diferentes tarefas e serviços que já estão disponíveis de maneira não bloqueante.


Uma tarefa bloqueante bastante comum é coletar dados de um serviço HTTP. Para isso vou usar a excelente biblioteca aiohttp que efetua chamadas não bloqueantes a serviços HTTP. Neste exemplo vamos coletar dados da API pública do Github e ler apenas o valor de Date do responde header.

import time
import urllib.request
import asyncio
import aiohttp

URL = 'https://api.github.com/events'
MAX_CLIENTS = 3


def fetch_sync(pid):
    print('Captura síncrona {} iniciou'.format(pid))
    start = time.time()
    response = urllib.request.urlopen(URL)
    datetime = response.getheader('Date')

    print('Processo {}: {}, demorou: {:.2f} segundos'.format(
        pid, datetime, time.time() - start))

    return datetime


@asyncio.coroutine
def fetch_async(pid):
    print('Captura assíncrona {} iniciou'.format(pid))
    start = time.time()
    response = yield from aiohttp.request('GET', URL)
    datetime = response.headers.get('Date')

    print('Processo {}: {}, demorou: {:.2f} segundos'.format(
        pid, datetime, time.time() - start))

    response.close()
    return datetime


def synchronous():
    start = time.time()
    for i in range(1, MAX_CLIENTS + 1):
        fetch_sync(i)
    print("Processo demorou: {:.2f} segundos".format(time.time() - start))


@asyncio.coroutine
def asynchronous():
    start = time.time()
    tasks = [asyncio.ensure_future(
        fetch_async(i)) for i in range(1, MAX_CLIENTS + 1)]
    yield from asyncio.wait(tasks)
    print("Processo demorou: {:.2f} segundos".format(time.time() - start))


print('Sincrono:')
synchronous()

print('Assíncrono:')
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

Execute:

$ python3 -V
Python 3.4.4+

$ pip3 install aiohttp

$ python3 async4.py
Sincrono:
Processo sincrono 1 iniciou
Processo 1: Wed, 17 Feb 2016 13:10:11 GMT, demorou: 0.54 segundos
Processo sincrono 2 iniciou
Processo 2: Wed, 17 Feb 2016 13:10:11 GMT, demorou: 0.50 segundos
Processo sincrono 3 iniciou
Processo 3: Wed, 17 Feb 2016 13:10:12 GMT, demorou: 0.48 segundos
Process demorou: 1.54 segundos

Assíncrono:
Processo assincrono 1 iniciou
Processo assincrono 2 iniciou
Processo assincrono 3 iniciou
Processo 3: Wed, 17 Feb 2016 13:10:12 GMT, demorou: 0.50 segundos
Processo 2: Wed, 17 Feb 2016 13:10:12 GMT, demorou: 0.52 segundos
Processo 1: Wed, 17 Feb 2016 13:10:12 GMT, demorou: 0.54 segundos
Processo demorou: 0.54 segundos

Nota: requer Python 3.4.4+ caso contrário cairá em exception

Primeiramente, repare na diferença de tempo, usando chamadas assíncronas nós efetuamos as requisições ao serviço HTTP exatamente ao mesmo tempo (13:10:12). Como falado anteriormente, cada requisição passou (yield) o fluxo de controle para a próxima e retornou quando foi completada.

Resultando no fato de que requisitar e capturar os resultados de todos as tarefas demorou o mesmo tempo que a requisição mais lenta! Veja o tempo no log 0.54 segundos para a requisição mais lenta (processo 1) e é exatamente o mesmo tempo que se passou para processar todos os 3 requests, Legal né? (enquanto a parte de I/O daquela tarefa lenta estava bloqueada, as outras puderam ser executadas simultaneamente).

Agora veja como o código é similar ao da versão síncrona! é praticamente o mesmo código! As diferenças principais são por conta das diferenças de implementações das bibliotecas usadas e a parte da criação das tasks e a espera para elas terminarem.

Criando concorrência

Até então estamos usando uma única abordagem de criar e requisitar resultados de coroutines, criar uma lista de tasks e esperar que elas terminem.

Mas as coroutines podem ser programadas para serem executadas e requisitar resultados em maneiras diferentes. Imagine um cenário onde precisamos processar os resultados de uma chamada HTTP GET assim que ela é requisitada, o processo é na verdade similar ao que fizemos no exemplo anterior.

import time
import random
import asyncio
import aiohttp

URL = 'https://api.github.com/events'
MAX_CLIENTS = 3


@asyncio.coroutine
def fetch_async(pid):
    start = time.time()
    sleepy_time = random.randint(2, 5)
    print('Processo assincrono {} iniciou, esperando por {} segundos'.format(
        pid, sleepy_time))

    yield from asyncio.sleep(sleepy_time)

    response = yield from aiohttp.request('GET', URL)
    datetime = response.headers.get('Date')

    response.close()
    return 'Processo {}: {}, demorou: {:.2f} segundos'.format(
        pid, datetime, time.time() - start)


@asyncio.coroutine
def asynchronous():
    start = time.time()
    futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
    for i, future in enumerate(asyncio.as_completed(futures)):
        result = yield from future
        print('{} {}'.format(">>" * (i + 1), result))

    print("Processo demorou: {:.2f} segundos".format(time.time() - start))


ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

Execute

$ python3 async5.py

Processo assincrono 1 iniciou, esperando por 4 segundos
Processo assincrono 3 iniciou, esperando por 5 segundos
Processo assincrono 2 iniciou, esperando por 3 segundos
>> Processo 2: Wed, 17 Feb 2016 13:55:19 GMT, demorou: 3.53 segundos
>>>> Processo 1: Wed, 17 Feb 2016 13:55:20 GMT, demorou: 4.49 segundos
>>>>>> Processo 3: Wed, 17 Feb 2016 13:55:21 GMT, demorou: 5.48 segundos
Processo demorou: 5.48 segundos

Repare no deslocamento >> e no tempo de cada chamada, elas foram programadas ao mesmo tempo, os resultados chegam fora de ordem e são processados assim que chegam.

Este código é um pouco diferente, estamos agrupando as coroutines em uma lista, cada uma para ser agendada e executada. a função as_completed retorna um iterador que irá gerar (YIELD) um future completo assim que a tarefa estiver terminada. Muito legal né? aliás, as funções as_completed e wait são ambas originalmente parte do modulo concurrent.futures


Vamos pegar um outro exemplo, imagine que você está tentando consultar o seu endereço de IP público atual em seu programa. Existem alguns serviços que fornecem essa informação mas você não tem certeza se estarão acessíveis no momento da execução. Você não quer checar cada um deles sequencialmente, então, é preferível efetuar requisições concorrentes para cada um dos serviços e utilizar aquele que responder mais rapidamente, ok? Ok!

Bom, acontece que nosso velho amigo wait recebe o parâmetro return_when para fazer exatamente isso. Estávamos ignorando o dado retornado pelo wait já que estávamos preocupados apenas em paralelizar as tarefas. Mas agora nós queremos pegar os resultados da coroutine, então usaremos dois conjuntos de futures, done e pending.

from collections import namedtuple
import time
import asyncio
from concurrent.futures import FIRST_COMPLETED
import aiohttp

Service = namedtuple('Service', ('name', 'url', 'ip_attr'))

SERVICES = (
    Service('ipify', 'https://api.ipify.org?format=json', 'ip'),
    Service('ip-api', 'http://ip-api.com/json', 'query')
)


@asyncio.coroutine
def fetch_ip(service):
    start = time.time()
    print('Fetching IP from {}'.format(service.name))

    response = yield from aiohttp.request('GET', service.url)
    json_response = yield from response.json()
    ip = json_response[service.ip_attr]

    response.close()
    return '{} terminou com resultado: {}, demorou: {:.2f} segundos'.format(
        service.name, ip, time.time() - start)


@asyncio.coroutine
def asynchronous():
    futures = [fetch_ip(service) for service in SERVICES]
    done, pending = yield from asyncio.wait(
        futures, return_when=FIRST_COMPLETED)
    print(done.pop().result())


ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

Execute:

$ python3 async6.py
Fetching IP from ip-api
Fetching IP from ipify
ip-api terminou com resultado: 82.34.76.170, demorou: 0.09 segundos
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10f95c6d8>
Task was destroyed but it is pending!
task: <Task pending coro=<fetch_ip() running at 2c-fetch-first-ip-address-response.py:20> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_connect_done(10)(), Task._wakeup()]>>

Espere, o que aconteceu aqui? O primeiro serviço respondeu com sucesso então o que são esses warnings?

Bem, nós agendamos duas tarefas mas não permitimos que as duas fossem completadas. o AsyncIO considera que é um bug e imprime um warning. Então queremos informar ao event loop que este era um comportamento esperado e não se preocupar com a segunda tarefa que ficou pendente. Como? que bom que perguntou.

Estados do Futuro

(sobre o estado que um future está atualmente, não o estado que ele estará no futuro... você entendeu né!)

Eles são:

  • Pending
  • Running
  • Done
  • Cancelled

Simples assim, quando um future finaliza sua tarefa, esta tarefa irá retornar o resultado de volta para o future, se estiver em pending ou cancelled ele gera o erro InvalidStateError ou CancelledError, e finalmente se a coroutine gera o erro ele será re-gerado, o que significa o mesmo comportamento ao chamar exception. confira aqui

Você também pode usar .done, .cancelled e .running em uma Future para obter um booleano indicando o estado. Note que done significa simplesmente que o result irá retornar ou gerar um erro. Você pode explicitamente cancelar um Future chamando o método cancel, e isso é o que precisamos para resolver o warning anterior.



def asynchronous():
    futures = [fetch_ip(service) for service in SERVICES]
    done, pending = yield from asyncio.wait(
        futures, return_when=FIRST_COMPLETED)
    print(done.pop().result())

    for future in pending:
        future.cancel()

e então

$ python3 async6.py
Fetching IP from ip-api
Fetching IP from ipify
ip-api terminou com resultado: 82.34.76.170, demorou: 0.09 segundos

Um bom resultado né!

Futures permitem injetar callbacks a serem executados quando entrarem no estado done caso você queira adicionar uma lógica adicional, ou se você não quiser usar o yield from e prefira o callback hell, (sério quer mesmo?)

E você pode, para objetivos de unit-testing manualmente injetar o resultado ou uma exception a um Future.

Gerenciamento de erros

O AsyncIO é sobre fazer código concorrente gerenciável e legível, e isto se torna óbio no que diz respeito ao tratamento de erros. Vamos voltar ao exemplo anterior para ilustrar isso.

Imagine que queremos garantir que os 2 serviços retornaram o mesmo resultado, mas um dos serviços fica offline e não responde, podemos usar apenas o usual try... except

from collections import namedtuple
import time
import asyncio
from concurrent.futures import FIRST_COMPLETED
import aiohttp

Service = namedtuple('Service', ('name', 'url', 'ip_attr'))

SERVICES = (
    Service('ipify', 'https://api.ipify.org?format=json', 'ip'),
    Service('ip-api', 'http://ip-api.com/json', 'query'),
    Service('broken', 'http://este-servico-nao-funciona', 'ip')
)


@asyncio.coroutine
def fetch_ip(service):
    start = time.time()
    print('Fetching IP from {}'.format(service.name))

    try:
        response = yield from aiohttp.request('GET', service.url)
    except:
        return "{} não está respondendo".format(service.name)

    json_response = yield from response.json()
    ip = json_response[service.ip_attr]

    response.close()
    return '{} terminou com resultado: {}, demorou: {:.2f} segundos'.format(
        service.name, ip, time.time() - start)


@asyncio.coroutine
def asynchronous():
    futures = [fetch_ip(service) for service in SERVICES]
    done, _ = yield from asyncio.wait(futures)

    for future in done:
        print(future.result())


ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

execute:

$ python3 async7.py
Fetching IP from ip-api
Fetching IP from borken
Fetching IP from ipify
ip-api terminou com o resultado: 85.133.69.250, demorou: 0.75 segundos
ipify terminou com o resultado: 85.133.69.250, demorou: 1.37 segundos
borken não está respondendo

Também podemos tratar os erros enquanto processamos os resultados dos Futures em caso de algo não esperado acontecer (lembra que eu disse que os erros são re-gerados na coroutine).

from collections import namedtuple
import time
import asyncio
from concurrent.futures import FIRST_COMPLETED
import aiohttp
import traceback

Service = namedtuple('Service', ('name', 'url', 'ip_attr'))

SERVICES = (
    Service('ipify', 'https://api.ipify.org?format=json', 'ip'),
    Service('ip-api', 'http://ip-api.com/json', 'query'),
    Service('broken', 'http://este-servico-nao-funciona', 'ip')
)


@asyncio.coroutine
def fetch_ip(service):
    start = time.time()
    print('Fetching IP from {}'.format(service.name))

    try:
        response = yield from aiohttp.request('GET', service.url)
    except:
        return "{} não está respondendo".format(service.name)

    json_response = yield from response.json()
    ip = json_response[service.ip_attr]

    response.close()
    return '{} terminou com resultado: {}, demorou: {:.2f} segundos'.format(
        service.name, ip, time.time() - start)


@asyncio.coroutine
def asynchronous():
    futures = [fetch_ip(service) for service in SERVICES]
    done, _ = yield from asyncio.wait(futures)

    for future in done:
        try:
            print(future.result())
        except:
            print("Erro não esperado: {}".format(traceback.format_exc()))


ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

Veja:

$ python3 async7.py
Fetching IP from ipify
Fetching IP from borken
Fetching IP from ip-api
ipify terminou com o resultado: 85.133.69.250, demorou: 0.91 segundos
borken não está respondendo
Erro não esperado: Traceback (most recent call last):
 File “3b-fetch-ip-addresses-future-exceptions.py”, line 41, in asynchronous
 print(future.result())
 File “/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/futures.py”, line 274, in result
 raise self._exception
 File “/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/tasks.py”, line 239, in _step
 result = coro.send(value)
 File “3b-fetch-ip-addresses-future-exceptions.py”, line 27, in fetch_ip
 ip = json_response[service.ip_attr]
KeyError: ‘this-is-not-an-attr’

Da mesma forma que agendar uma task e não esperar que ela termine é considerado um bug, agendar uma task e não recuperar possíveis erros também irá gerar um warning.

from collections import namedtuple
import time
import asyncio
import aiohttp

Service = namedtuple('Service', ('name', 'url', 'ip_attr'))

SERVICES = (
    Service('ipify', 'https://api.ipify.org?format=json', 'ip'),
    Service('ip-api', 'http://ip-api.com/json', 'this-is-not-an-attr'),
    Service('borken', 'http://no-way-this-is-going-to-work.com/json', 'ip')
)


@asyncio.coroutine
def fetch_ip(service):
    start = time.time()
    print('Fetching IP from {}'.format(service.name))

    try:
        response = yield from aiohttp.request('GET', service.url)
    except:
        print('{} is unresponsive'.format(service.name))
    else:
        json_response = yield from response.json()
        ip = json_response[service.ip_attr]

        response.close()
        print('{} finished with result: {}, took: {:.2f} seconds'.format(
            service.name, ip, time.time() - start))


@asyncio.coroutine
def asynchronous():
    futures = [fetch_ip(service) for service in SERVICES]
    yield from asyncio.wait(futures)  # intentionally ignore results


ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous())
ioloop.close()

execute:

$ python3 async8.py
Fetching IP from ipify
Fetching IP from borken
Fetching IP from ip-api
borken is unresponsive
ipify finished with result: 85.133.69.250, took: 0.78 seconds
Task exception was never retrieved
future: <Task finished coro=<fetch_ip() done, defined at 3c-fetch-ip-addresses-ignore-exceptions.py:15> exception=KeyError(‘this-is-not-an-attr’,)>
Traceback (most recent call last):
 File “/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/tasks.py”, line 239, in _step
 result = coro.send(value)
 File “3c-fetch-ip-addresses-ignore-exceptions.py”, line 26, in fetch_ip
 ip = json_response[service.ip_attr]
KeyError: ‘this-is-not-an-attr’

Se parece muito com a saída do exemplo anterior, mas não contém os mínimos detalhes e mensagens do asyncio.

Timeouts

E se nós não nos importarmos muito com o nosso IP? Imagine que é apenas um adicional ao nosso serviço, mas não importante, não queremos que o usuário fique esperando por este dado. Idealmente nós definimos um time-out para nossas tarefas não bloqueantes, e então continuamos nosso programa sem o atributo do IP já que neste exemplo não é tão importante.

De novo descobrimos que wait tem o atributo que precisamos:

import time
import random
import asyncio
import aiohttp
import argparse
from collections import namedtuple
from concurrent.futures import FIRST_COMPLETED

Service = namedtuple('Service', ('name', 'url', 'ip_attr'))

SERVICES = (
    Service('ipify', 'https://api.ipify.org?format=json', 'ip'),
    Service('ip-api', 'http://ip-api.com/json', 'query'),
)

DEFAULT_TIMEOUT = 0.01


@asyncio.coroutine
def fetch_ip(service):
    start = time.time()
    print('Fetching IP from {}'.format(service.name))

    yield from asyncio.sleep(random.randint(1, 3) * 0.1)
    try:
        response = yield from aiohttp.request('GET', service.url)
    except:
        return '{} is unresponsive'.format(service.name)

    json_response = yield from response.json()
    ip = json_response[service.ip_attr]

    response.close()
    print('{} finished with result: {}, took: {:.2f} seconds'.format(
        service.name, ip, time.time() - start))
    return ip


@asyncio.coroutine
def asynchronous(timeout):
    response = {
        "message": "Result from asynchronous.",
        "ip": "not available"
    }

    futures = [fetch_ip(service) for service in SERVICES]
    done, pending = yield from asyncio.wait(
        futures, timeout=timeout, return_when=FIRST_COMPLETED)

    for future in pending:
        future.cancel()

    for future in done:
        response["ip"] = future.result()

    print(response)


parser = argparse.ArgumentParser()
parser.add_argument(
    '-t', '--timeout',
    help='Timeout to use, defaults to {}'.format(DEFAULT_TIMEOUT),
    default=DEFAULT_TIMEOUT, type=float)
args = parser.parse_args()

print("Using a {} timeout".format(args.timeout))
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(asynchronous(args.timeout))
ioloop.close()

Repare no argumento timeout, também adicionamos um parâmetro de linha de comando para testar mais facilmente o que acontecerá se deixarmos a requisição ocorrer algumas vezes. Também adicionei um tempo randomico de slepp só para garantir que as coisas não aconteçam tão rápido que a gente nem perceba.

$ python async8.py

Using a 0.01 timeout
Fetching IP from ipify
Fetching IP from ip-api
{‘message’: ‘Result from asynchronous.’, ‘ip’: ‘not available’}

$ python async8.py -t 5
Using a 5.0 timeout
Fetching IP from ip-api
Fetching IP from ipify
ipify finished with result: 82.34.76.170, took: 1.24 seconds
{'ip': '82.34.76.170', 'message': 'Result from asynchronous.'}

Conclusão

Asyncio aumentou meu já enorme amor por Python. Para ser absolutamente honesto eu me apaixonei pelas coroutines em Python quando conheci o Tornado mas o asyncio conseguiu unir o melhor desta abordagem junto com excelentes bibliotecas de concorrência. E muito foi feito para que outras bibliotecas possam usar o IO loop, então se você está usando o Tornado, você também pode usa-lo com bibliotecas feitas para o asyncio!

E como eu disse anteriormente o maior problema por enquanto é a falta de bibliotecas e módulos que implementam o comportamento não bloqueante. Você pode achar que uma vasta quantidade de tecnologias já estabelecidas ainda não tenham uma versão não bloqueante para interagir com asyncio, ou que as existentes ainda estão jovens ou experimentais. Porém, O numero de bibliotexas está crescendo diariamente

confira este repo: https://github.com/aio-libs

Espero neste tutorial ter passado a idéia de como é prazeroso trabalhar com AsyncIO. Eu honestamente penso que isto é a peça que finalmente nos levará a adoção em massa e adaptação ao Python 3, essas são as coisas que você está perdendo se ficar parado no Python 2.7.

Uma coisa é certa, o Futuro do Python mudou completamente! (trocadilho intencional)

por Bruno Rocha em 27 de November de 2016 às 13:06

November 23, 2016

PythonClub

Deploy rápido e simples com Dokku

Sempre busquei alternativas para deploy simples como o heroku. Vou mostrar neste passo-a-passo uma forma simples e rápida utilizando o Dokku.

Dokku é a menor implementação PaaS que você já viu. De uma forma simples e rápida consegue-se configurar um servidor para deploy. Se existe alguma dúvida sobre PaaS, SaaS, etc., uma pesquisa rápida no google vai retornar várias referências.

Nesse exemplo vou utilizar uma vps básica na DigitalOcean. Uma máquina 512 MB / 20 Gb/ 1 CPU, rodando Ubuntu Server 16.04.1. É possível criar uma máquina já com Dokku instalado e pré-configurado mas vou fazer com uma máquina 'limpa' para servir de base para outras vps.

Instalando

Com o servidor em execução vamos acessar via ssh pelo terminal. No caso de utilizar OS Windows, utilize o putty para acessar o servidor.

ssh [ip-do-servidor] -l root

No primeiro acesso confirme com yes o questionamento sobre a autenticidade. No caso da DigitalOcean vai ser solicitado a mudança de senha nesse primeiro acesso.

Seguindo a documentação do Dokku vamos realizar a instação. Este processo demora +- uns 10 minutos.

wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh
sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh

Finalizado o script de instação vamos adicionar a chave publica de nosso usuário de desenvolvimento para conseguir fazer o deploy no servidor recem criado.

Chaves

Na nossa máquina de desenvolvimento, vamos checar se nosso usuário tem uma chave pública:

ls -al ~/.ssh

Por padrão os arquivos das chaves públicas podem ser:

  • id_dsa.pub
  • id_ecdsa.pub
  • id_ed25519.pub
  • id_rsa.pub

No meu caso eu tenho o id_rsa.pub, então vou ler o conteúdo, seleciona-lo e copiar:

cat ~/.ssh/id_rsa.pub

Para gerar a chave: (caso não exista nenhum arquivo .pub na pasta ~/.ssh)

ssh-keygen -t rsa

Aceite as três opções pedidas por default. (não inserir password) Para OS Windows achei este artigo. ssh Windows (não testei)

Inserindo a chave pública no servidor

Com nossa chave pública copiada (ctrl+c) vamos abrir o browser e digitar o ip do nosso servidor. Vai aparecer uma tela como a da imagem a seguir: No campo Public Key, colamos nossa chave (Ctrl+V) e depois é so clicar em Finish Setup. Feito isto você vai ser redirecionado para página da documentação do Dokku.

Criando nossa APP

No terminal, conectado no nosso servidor:

dokku apps:create [nome-app]

No meu caso:

dokku apps:create fjfundo

Para listar as apps existentes:

dokku apps

Quando você cria um novo aplicativo, por padrão o dokku nao fornece nenhum banco de dados como MySql ou PostgreSQL. É preciso instalar plugins. Dokku tem plugins oficiais para banco de dados. Neste passo-a-passo vou utilizar o PostgreSQL.

Instalando o plugin postgres e configurando o serviço de banco de dados

No terminal, conectado no nosso servidor:

sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git

Criando o serviço postgres:

dokku postgres:create [nome-servico]

No meu caso:

dokku postgres:create fjfundo-database

Vamos criar o link entre os serviços de banco de dados e nossa app.

dokku postgres:link [nome-servico] [nome-app]

No meu caso:

dokku postgres:link fjfundo-database fjfundo

Configurando e executando nosso primeiro deploy.

Na nossa máquina cliente vamos configurar o git para fazer o primeiro deploy. Vamos adicionar nosso repositorio remoto dokku da seguinte forma:

git remote add dokku dokku@[ip-do-servidor]:[nome-app]

No meu caso:

git remote add dokku dokku@xxx.xxx.xxx.xxx:fjfundo

No meu caso antes de fazer o deploy eu tenho que configurar algumas variáveis de ambiente como DEBUG e SECRET_KEY. Para esta configuração executo os comandos no servidor. Vou seguir a documentação existente em Environment Variables

dokku config:set fjfundo DEBUG='False'
dokku config:set fjfundo SECRET_KEY='sua secret_key'

Para listar as variáveis de ambiente de nossa app:

dokku config [nome da app]

Feito isto vamos ao nosso primeiro deploy: ( na nossa máquina cliente/dev)

git push dokku master --force

Pronto! Agora é so acessar nossa aplicação. No final do deploy o dokku mostra a url de conexão. Caso precise obter a url use o comando no servidor:

dokku url [nome-app]

Criando as tabelas do banco de dados

Nossa app está no ar mais ainda não tem as tabelas de nossa base de dados.

dokku run fjfundo python manage.py migrate

Troque fjfundo pelo nome da sua app.

Considerações finais

Ainda estou estudando e aprendendo a utilizar o dokku. Utilizo apenas em ambiente de desenvolvimento mas pretendo utilizar em produção. Ainda tenho muito que aprender e fica aqui meu email, joseadolfojr@gmail.com, para quem tiver alguma dúvida e quizer também contribuir para meus estudos.

por Júnior Carvalho em 23 de November de 2016 às 19:30

November 22, 2016

Magnun Leno

November 21, 2016

Aprenda Python

Por que TDD?

**Observações:** 1. Esse artigo é focado em iniciantes, por isso eu resolvi usar um estilo mais popular, evitando termos muito técnicos relacionados a testes de software. 1. Não pretendo esgotar o assunto sobre TDD nem sobre teste de software, que são campos muito amplos de estudo. O que busco aqui é simplesmente fazer uma introdução didática ao assunto. Agora, vamos ao que interessa.

por Vinicius Assef (noreply@blogger.com) em 21 de November de 2016 às 13:02

TDD me torna um programador mais rápido?

TDD é uma das práticas básicas do [Extreme Programming](http://www.extremeprogramming.org/index.html) (XP), que, por sua vez, é uma das abordagens para desenvolvimento ágil de software. Por causa disso, muitas pessoas pensam assim: > "Metodologia ágil" chama-se "ágil", porque significa "mais rápido". Portanto, se TDD faz parte disso, vai fazer de mim um programador mais rápido. A questão é

por Vinicius Assef (noreply@blogger.com) em 21 de November de 2016 às 11:09

November 20, 2016

Bruno Cezar Rocha

Castálio Podcast Especial Python Brasil Parte 3

Python Brasil parte 3

Fechando a série sobre a Python Brasil 12, neste episódio Eu, Og e Elyézer falamos sobre assuntos abordados em algumas palestras da conferência, comunidade, cervejas e Oktoberfest!

Como ouvir?

Acesse a seguinte URL http://castalio.info/episodio-75-python-brasil-12-parte-3.html para ouvir online e você também pode baixar os arquivos em MP3 ou Ogg.

Acompanhe!

Participe!

Se você tem sugestões, dicas de pessoas para serem entrevistadas, pautas a serem abordadas por favor entre em contato em um dos canais acima ou deixe comentários nos episódios.

por Bruno Rocha em 20 de November de 2016 às 13:11

November 12, 2016

Bruno Cezar Rocha

Castálio Podcast Especial Python Brasil Parte 2

Entrevistas na Python Brasil parte 2

Na segunda parte falamos com o pessoal do Projeto Serenata de Amor e depois com o Turicas mas não vou dar spoiler, vocês terão que ouvir o episódio para saber mais sobre a conversa.

E na semana que vem tem a terceira parte!

Como ouvir?

Acesse a seguinte URL http://castalio.info/episodio-74-python-brasil-12-parte-2.html para ouvir online e você também pode baixar os arquivos em MP3 ou Ogg.

Acompanhe!

Participe!

Se você tem sugestões, dicas de pessoas para serem entrevistadas, pautas a serem abordadas por favor entre em contato em um dos canais acima ou deixe comentários nos episódios.

por Bruno Rocha em 12 de November de 2016 às 14:07

November 08, 2016

Aprenda Python

TDD ajuda a escrever programas mais simples?

Praticamente todo mundo que nunca tentou a abordagem Test First para escrever programas, pergunta: "Por que dizem que isso é melhor?" Vou contar uma história real para você entender melhor. Durante muito tempo eu desenvolvi software com a solução técnica em mente. Como todo principiante, eu ouvia o problema do cliente e logo começava a pensar nas tabelas, nos campos e nos relacionamentos que

por Vinicius Assef (noreply@blogger.com) em 08 de November de 2016 às 15:17

November 07, 2016

Bruno Cezar Rocha

Castálio Podcast Especial Python Brasil

Castálio?

Castália é o nome de uma náiade (uma ninfa aquática) que foi transformada por Apolo em nascente de água, perto de Delfos (a Fonte de Castália) e na base do Monte Parnaso.

Castália inspirava o génio poético daqueles que bebessem das suas águas ou ouvissem o movimento das suas águas. A água sagrada também era usada para as limpezas dos templos em Delfos.

Com o objetivo de entrevistar e ao mesmo tempo apresentar pessoas e projetos de tecnologia que sejam fonte de inspiração para os ouvintes, este podcast traz peridicamente uma nova vítima, err figura da comunidade de tecnologia que será sabatinada de todos os ângulos para o seu deleite!

Castálio na Python Brasil 12

Durante a Python Brasil 12 em Florianópolis eu e o Elyézer gravamos algumas entrevistas para o Castálio Podcast e com isso surgiu o convite do Og Maciel para eu integrar a equipe do Castálio, publicaremos 3 episódios especiais sore a Python Brasil 12, dois episódios com entrevistas e um terceiro com detalhes sobre nossa participação e o que fizemos por lá.


Lightning Cast

A idéia surgiu de ultima hora, em uma conversa de IRC durante o trabalho pensamos que seria interessante gravar pequenas entrevistas e ai surgiu o conceito de Lightning Cast! Paramos algumas pessoas no corredor da PyBR e convidamos para falar durante +-5 minutos.

Como foi a primeira vez que fizemos e bastante improvisado conseguimos fazer apenas 4 entrevistas, mas já nos serviu para validar a idéia e agora este novo formato fará parte do Castálio sempre que estivermos em eventos e conferências.

Entrevistas

Entrevistamos na primeira parte o Mário Sérgio que foi o Big Kahuna (organizador) da conferência e também a Naomi Cedar que foi Keynote do evento e faz parte da Python Software Foundation além de ser porta voz da questão de diversidade e inclusão na comunidade Python (esta segunda entrevista foi feita em Inglês).

Na segunda parte falamos com o pessoal do Projeto Serenata de Amor e depois com o Turicas mas não vou dar spoiler, vocês terão que ouvir o episódio na próxima semana para saber mais sobre a conversa.

Na terceira parte que será publicada daqui 2 semanas, Eu, Elyézer e o Og iremos bater um papo sobre a Python Brasil, Palestras que assistimos, pessoas que conhecemos e cervejas que tomamos, além é claro que falar sobre Floripa e a OktoberFest que foi o evento de fechamento da conferência.

Como ouvir?

Acesse a seguinte URL http://castalio.info/episodio-73-python-brasil-12-parte-1.html para ouvir online e você também pode baixar os arquivos em MP3 ou Ogg.

Acompanhe!

Participe!

Se você tem sugestões, dicas de pessoas para serem entrevistadas, pautas a serem abordadas por favor entre em contato em um dos canais acima ou deixe comentários nos episódios.

por Bruno Rocha em 07 de November de 2016 às 13:57

November 03, 2016

Aprenda Python

Entendendo virtualenvs

Se você é iniciante em Python, provavelmente já ouviu falar em _virtualenv_. Todo mundo aconselha usar, mas para que serve um _virtualenv_? Que problema ele resolve, afinal? O que é um virtualenv? ---------------------- Em poucas palavras, o virtualenv permite que várias versões de uma mesma biblioteca possam conviver no mesmo computador, sem conflitos. Vou exemplificar. Suponhamos que

por Vinicius Assef (noreply@blogger.com) em 03 de November de 2016 às 18:41

November 02, 2016

Thiago Avelino

O que é responsabilidade?

O que é responsabilidade?

Responder por suas ações e consequências do seus atos!

Se você não é capaz de responder pela ação que você toma volte pensar no que você anda fazendo.
Errar é humano, passar a mão na cabeça de quem erra e lidar como nada tivesse acontecido, é justificar o erro como um acerto.

por Avelino em 02 de November de 2016 às 15:58

Parabéns pelo projeto, sem ele seria impossível manter o deploy do @filmow e @nuveohq tão rápido…

Parabéns pelo projeto, sem ele seria impossível manter o deploy do @filmow e @nuveohq tão rápido como uma aplicação pequena.

por Avelino em 02 de November de 2016 às 15:30

October 29, 2016

Thiago Avelino

Osvaldo Matos Jr.

Osvaldo Matos Jr. (Tupy)parabéns pela iniciativa de organizar um evento, precisamos de mais eventos assim.

por Avelino em 29 de October de 2016 às 03:20

Filipe Saraiva

Meu QtCon + Akademy 2016

De 31 de agosto à 10 de setembro estive em Berlim participando de duas fantásticas conferências: QtCon e Akademy.

QtCon reuniu cinco diferentes comunidades para que elas realizassem suas respectivas conferências em um mesmo tempo e espaço, criando assim um grande e diversificado evento. As comunidades participantes foram o Qt, KDAB, KDE (celebrando seu aniversário de 20 anos), VLC e FSFE (ambas comemorando 15 anos de atividades).

bcc

Principal sala de conferência do QtCon no bcc

Esta diversidade de temas foi uma interessante característica do QtCon. Realmente gostei muito de assistir apresentações de pessoas do Qt e KDAB, e também fiquei surpreso com os temas relacionados à comunidade do VLC. Os avançados aspectos técnicos de apresentações sobre Qt em mobile, Qt em IoT (incluindo carros autônomos), o futuro do Qt, Qt + Python, como contribuir para o Qt, e mais, chamaram minha atenção durante a conferência.

Sobre o VLC eu fui surpreendido pelo tamanho da comunidade. Nunca imaginei que o VLC teria tantos desenvolvedores… de fato, nunca pensei que a VideoLAN na verdade é um guarda-chuva para vários projetos relacionados com multimídia, como codecs, ferramentas de streaming, ports do VLC para dispositivos específicos (incluindo carros via Android Auto), e mais. Apreciei bastante encontrar este pessoal e assistir às suas apresentações.

Estava na expectativa que o VLC 3.0 fosse lançado durante a QtCon, mas infelizmente isto não ocorreu. É claro, o time de desenvolvedores está melhorando este novo release, e quando ele estiver finalizado terei um VLC para utilizar junto com meu Chromecast. Portanto, mantenham o bom trabalho cabeças de cone!

As apresentações da FSFE foram interessantes também. No Brasil é comum termos palestras sobre os aspectos políticos e filosóficos do software livre em conferências como o FISL e Latinoware. Na QtCon, FSFE trouxe este tipo de apresentação no “estilo europeu”: algumas vezes as apresentações pareciam ser um pouco mais pragmáticas em suas abordagens. Outras apresentações da FSFE falaram sobre a infraestrutura e aspectos organizacionais da fundação, uma visão geral interessante para compararmos com outros grupos como a ASL.org no Brasil.

E claro, também tivemos várias apresentações dos nossos companheiros cabeças de engrenagens. Destaco as palestras sobre a história do KDE, as novidades do Plasma, o estado do Plasma Mobile, KF5 no Android, a experiência do Minuet no mundo mobile, entre outras.

O anúncio da KDE Store foi uma novidade interessante e espero que ela trará mais atenção para o ecossistema do KDE quando pacotes multidistros (snap/flat/etc) estiverem disponíveis na loja.

Outro software que chamou minha atenção foi o Peruse, um leitor de quadrinhos. Espero que os desenvolvedores resolvam os atuais entraves para lançarem o quanto antes uma versão mobile, pois assim esse software poderá alcançar uma boa base de usuários nessas plataformas.

Ao final do QtCon, o Akademy teve início na universidade TU Berlin, em um belo e confortável campi. Esta fasse da conferência foi repleta de sessões e discussões técnicas, hacking e diversão.

Eu participei dos BoFs sobre Flatpack, Appstream, e Snapcraft. Houveram avançadas discussões técnicas sobre estes temas. Em todo Akademy fico impressionado com o avançado nível das discussões empreendidas pelos hackers do KDE. Esses caras são foda!

O BoF do Snapcraft foi um tutorial sobre como usar esta tecnologia para criar pacotes crossdistro de software com suas respectivas dependências. Foi muito interessante e gostaria de testar um pouco mais e também dar uma olhada no Flatpak, de forma que eu possa selecionar algum deles parar criar um pacote do Cantor.

Infelizmente perdi o BoF do Kube. Desejo muito um projeto PIM alternativo para o KDE, focado em E-Mail/Contatos/Calendário, e que seja mais econômico em termos de demanda de recursos computacionais. Estou mantendo minha atenção e expectativas nesse projeto.

Nos demais dias basicamente passei meu tempo trabalhando no Cantor e conversando com colegas do KDE de várias partes do mundo sobre diversos tópicos como KDE Edu, melhorias na nossa infraestrutura do Jabber/XMPP, KDE 20 anos, Plasma em computadores pequenos (valeu sebas pelo Odroid-C1+ 😉 ) WikiToLearn (seria interessante uma maneira de importar/exportar planilhas do Cantor do/para WikiToLearn?), e claro, ceveja e comida alemã.

Sobre Berlim? Esta foi minha segunda vez na cidade, e como na primeira fiquei bastante animado com a atmosfera multicultural da cidade, a comida (<3 porco <3), e as cervejas. Ficamos em Kreuzberg, um bairro hipster, onde pudemos visitar diferentes restaurantes e bares mantidos por imigrantes. Os eventos da QtCon+Akademy também foram interessantes, como a celebração da FSFE no c-base e o dia de descanso do Akademy na Ilha do Pavão.

Portanto, gostaria de dizer obrigado ao KDE e.V. por auxiliar na minha participação nos dois eventos, valeu Petra por nos ajudar com o hostel, e obrigado a todos os voluntários pelo trabalho duro que realizaram, fazendo esse Akademy uma verdadeira celebração da comunidade KDE.

photo_2016-10-25_11-54-34

Alguns brasileiros na QtCon/Akademy 2016: KDHelio, Lamarque, Sandro, João, Aracele, Filipe (eu)

por Filipe Saraiva em 29 de October de 2016 às 03:13

October 27, 2016

Filipe Saraiva

Meu LaKademy 2016

Foto em grupo LaKademy 2016

No final de maio, por volta de 20 gearheads de diferentes países da América Latina estiveram juntos no Rio de Janeiro, trabalhando em diferentes frentes do KDE. Este é o nosso “sprint de multiprojetos” chamado LaKademy!

A exemplo das edições anteriores do LaKademy, neste ano trabalhei pesado no Cantor; e ao contrário de edições anteriores, desta vez fiz algum trabalho em projetos que pretendo lançar em algum momento no futuro. Portanto, vamos ver meu relatório sobre o LaKademy 2016.

Cantor

LaKademy é muito importante na história do desenvolvimento do Cantor porque foram durante estes sprints que pude me focar e realizar importantes trabalhos de desenvolvimento que resultaram em novas funcionalidades para o software. Nas edições anteriores eu dei início ao desenvolvimento do backend para Python 2, portei o Cantor para Qt5/KF5, eliminei a kdelibs4support, e mais.

Este ano foi o primeiro LaKademy após receber o status de mantenedor di Cantor e, mais fantástico ainda, esta foi a primeira edição onde não fui o único a trabalhar no Cantor: tivemos um verdadeiro time trabalhando em diferentes partes do software.

Meu principal trabalho foi realizar uma pesada triagem nos bugs, fechando alguns antigos e confirmando outros tantos. Nessa tarefa pude corrigir alguns, como a renderização LaTeX e o crash após fechar a janela quando utilizando o backend do Sage, ou a correção dos comandos de plot para o backend do Octave.

Meu segundo trabalho foi auxiliar os outros desenvolvedores que estavam trabalhando no Cantor, algo que me deixou muito feliz e satisfeito. Ajudei o Fernando Telles, meu aluno do SoK 2015, para corrigir o backend do Sage para que ele pudesse suportar versões maiores que a 7.2. Wagner Reck trabalhou em um possível novo backend para Root, o framework para programação científica desenvolvido pelo CERN. Rafael Gomes criou uma imagem Docker do Cantor para tornar mais fácil a configuração do ambiente, compilação e contribuição de novos desenvolvedores. Ele quer utilizar isto em outros software do KDE, e fiquei muito feliz pelo Cantor ser o primeiro software a participar deste experimento.

Outro trabalho relevante foram algumas discussões com demais desenvolvedores para definirmos uma tecnologia “oficial” para criação de backends no Cantor. Atualmente, o Cantor tem 10 backends, desenvolvidos de diferentes formas: alguns deles utilizam APIs C/C++, outros usam Q/KProcess, alguns usam protocolo DBus… você pode imaginar o quão maluco é manter todos esses backends ao mesmo tempo.

Ainda não selecionei uma tecnologia para isso. Ambos DBus e Q/KProcess tem suas vantagens e desvantagens (DBus é uma solução mais “elegante” mas por outro lado torna Cantor muito difícil de ser levado para outras plataformas, algo que seria fácil se utilizássemos Q/KProcess)… bem, vou esperar o novo backend baseado em DBus para Julia, que será desenvolvido pelo meu aluno do GSoC 2016, após isso tomarei uma decisão sobre o que usar.

Da esquerda para a direita: Ronny, Fernando, Ícaro, e eu 😉

Novos projetos: Sprat e Leibniz (nomes não-oficiais)

Este ano pude trabalhar em alguns novos projetos que pretendo lançar no futuro. Seus nomes provisórios são Sprat e Leibniz.

Sprat é um editor de texto para escrever rascunhos de artigos científicos. Um texto científico segue alguns padrões de sentenças e figuras de linguagens. Pense sobre “Uma abordagem baseada em algoritmos genéticos foi aplicada ao problema do caixeiro viajante”: é fácil identificar o padrão nesse texto. Linguistas vem trabalhando nisso e é possível classificar sentenças baseadas no objetivo de comunicação a ser atingido por aquela sentença. Sprat permitirá ao usuário navegar em um conjunto de sentenças e selecioná-las para criar um rascunho de artigo científico. Eu planejo lançar o Sprat neste ano, portanto esperem por mais notícias logo mais.

Leibniz é Cantor sem planilhas (worksheets). Algumas vezes você quer apenas executar seu método matemático, seu script científico, e outros programas relacionados, sem colocar explicações, vídeos, figuras, e outros mais no termina. No KDE temos fantásticas tecnologias que nos permitem desenvolver uma interface “estilo Matlab” (KonsolePart, KTextEditor, QWidgets, e plugins) para todo tipo de linguagem de programação científica como Octave, Python, Scilab, R… apenas executando esses programas no KonsolePart já temos acesso ao destaque de sintaxe, complementação de código… eu gostaria de ter um software como esse, portanto iniciei esse desenvolvimento. Decidi criar um novo software e não apenas uma nova view pro Cantor porque penso que o código fonte do Leibniz será menor, e mais fácil de manter.

Então, se você se interessou por alguns destes projetos, comente abaixo para que eu fique sabendo e espere pelos próximos meses por mais novidades! 🙂

Trabalhos da comunidade

Durante o LaKademy tivemos nosso encontro de promoção, uma manhã inteira para discutir ações de promoção do KDE na América Latina. O KDE terá um dia de atividades no FISL e nós estamos animados para fazer um monte de festas de comemoração aos 20 anos do KDE nos principais eventos de software livre no Brasil. Também avaliamos e discutimos a continuação de algumas interessantes atividades como o Engrenagem (nossa série de videocasts) e novos projetos como vídeo de demonstrações de aplicações do KDE.

Nessa reunião também decidimos a cidade que sediará o LaKademy 2017: Belo Horizonte! Estamos na expectativa de termos um ano com incríveis atividades do KDE na América Latina para avaliarmos no próximo ano.

Conclusão: “O KDE na América Latina continua lindo

Nesta edição do LaKademy tivemos muito trabalho realizado por nossos dedicados colaboradores nas mais diferentes frentes do KDE, mas também tivemos momentos para ficarmos juntos e consolidar nossa comunidade e amizade. Infelizmente tivemos pouco tempo para conhecer o Rio de Janeiro (esta foi minha primeira vez na cidade), mas fiquei com uma boa impressão da cidade e das pessoas. Espero voltar aqui no futuro, talvez ainda este ano.

A melhor parte de ser um membro de uma comunidade como o KDE é fazer amigos para a vida, pessoas com quem você gosta de compartilhar bebidas e comidas enquanto conversa sobre qualquer coisa. Isso é muito legal para mim e pude encontrar bastante disso no KDE. <3

Valeu KDE e vejo vocês logo mais nos próximos LaKademies!

por Filipe Saraiva em 27 de October de 2016 às 19:11

October 23, 2016

PythonClub

Bot telegram mais web scraping - parte 1

Irei separa o artigo em 2 partes para não ficar extenso. Nessa primeira parte irei falar um pouco como criar um bot no telegram e como programa-lo para nos responder.

1 - Parte 1 - Bot simples. (você está aqui)

2 - Parte 2 - Bot e Web Scraping

Primeiro de tudo precisamos cria o bot, para isso usamos o próprio bot do telegram que faz isso para gente. Para isso bastar iniciar uma conversa com o @BotFather, ele irá nós da algumas opções:

/newbot - create a new bot
/token - generate authorization token
/revoke - revoke bot access token
/setname - change a bot's name
/setdescription - change bot description
/setabouttext - change bot about info
/setuserpic - change bot profile photo
/setinline - change inline settings
/setinlinegeo - toggle inline location requests
/setinlinefeedback - change inline feedback settings
/setcommands - change bot commands list
/setjoingroups - can your bot be added to groups?
/setprivacy - what messages does your bot see in groups?
/deletebot - delete a bot
/newgame - create a new game
/listgames - get a list of your games
/editgame - edit a game
/deletegame - delete an existing game
/cancel - cancel the current operation

As que nós interessa por enquanto são:

/newbot - Cria um novo bot. 
/setdescription - Adiciona uma descrição ao nosso bot.
/setuserpic - Adiciona uma imagem ao nosso bot.

Feito isso agora temos um token, que iremos usar para dar funções e vida ao bot. Para isso iremos usar a lib telegram-bot, ela irá facilitar a nosso vida, assim não iremos precisar mexer diretamente com a API do telegram.

Instalando telegram-bot utilizando o pip

pip install python-telegram-bot

Agora com a biblioteca instalada iremos programar um mini bot para nós falar as horas.

#!/usr/bin/env python3
# -*- coding:utf-8  -*-

from telegram.ext import Updater, CommandHandler
from time import strftime

up = Updater('Insira o token aqui.')


def Horas(bot, update):

    msg = "Olá {user_name} agora são: "
    msg += strftime('%H:%M:%S')

    bot.send_message(chat_id=update.message.chat_id,
                     text=msg.format(
                         user_name=update.message.from_user.first_name))


up.dispatcher.add_handler(CommandHandler('horas', Horas))
up.start_polling()

Entendendo o código.

1 - Importamos tudo que iremos utilizar.
2 - Informamos o token do nosso bot.
3 - Criamos uma função que pega a horas com strftime e responde no chat.
4 - Criamos um comando para o nosso bot, no caso o /horas.
5 - Startamos o bot.

Feito isso quando mandar um /horas para o bot ele irá nos responder com: "Olá SeuNome agora são Horas."

Caso você queira adicionar mais funções ao bot, aqui está a documentação da biblioteca.

Na próxima parte iremos escolher alguns site que fale sobre Python e fazer Scraping nele, assim sempre que ele tiver uma nova postagem nosso bot vai nós enviar uma mensagem informando.

por Pedro Souza. em 23 de October de 2016 às 22:30

October 14, 2016

Aprenda Python

Como hospedar meu site feito em Python?

Vou começar esse artigo pelo óbvio, mas que gera frustração em quem vem do PHP: hospedar Python é mais complicado do que hospedar PHP. Mas não é o fim do mundo. Pronto, falei. Agora, vamos seguir pelos itens básicos necessários para hospedar um projeto em Python. Servidor web ------------ Não tem jeito, como seu sistema vai rodar na web, você precisa de um servidor web. O mais famoso é o

por Vinicius Assef (noreply@blogger.com) em 14 de October de 2016 às 18:18

October 10, 2016

Aprenda Python

Por que properties são melhores que getters e setters

Quando vemos uma classe em Python escrita com métodos getters e setters, logo percebemos que o programador ainda não "pegou o jeito" de OOP em Python. Getters e setters são muito comuns em outras linguagens de programação porque os atributos normalmente são declarados com escopo privado. Ou seja, quem usa a classe não tem acesso aos atributos do objeto. Por isso criaram os métodos `get_...()` e

por Vinicius Assef (noreply@blogger.com) em 10 de October de 2016 às 12:12

October 03, 2016

Magnun Leno

Hack ‘n’ Cast v1.1 - O Controle em Suas Mãos

Teclado, mouse, gamepads, joysticks, controles, manches, alavancas... Seja qual for o nome/modalidade que você use, ele está sempre lá, "o controle de videogame".

Baixe o episódio e leia o shownotes

por Magnun em 03 de October de 2016 às 06:02

October 01, 2016

Python Help

Usando comando with para evitar acoplamento temporal

Hoje vamos falar sobre como melhorar um padrão que você provavelmente já viu em muito código:

    f = open(...)
    ....
    f.close()

 

    a = A(...)
    a.start(...)
    ...
    a.end()

 

    c = Coisa(...)
    c.cria(...)
    ....
    c.destroi()

 

    test = Test()
    test.setUp()
    test.run()
    test.tearDown()

Os trechos de código acima possuem em comum o que chamamos acoplamento temporal (do inglês, temporal coupling).

Acoplamento mede o quanto uma coisa depende de outra em software, e é comumente medida entre módulos ou componentes – tipo, o quanto certas classes ou funções dependem uma da outra. Em geral, é desejável que haja poucas interdependências, para evitar que a complexidade se espalhe em um projeto.

O acoplamento temporal acontece quando uma coisa precisa ser feita depois de outra, mesmo que seja dentro do mesmo módulo ou função. A relação de interdependência é com o tempo, isto é, o momento em que as coisas precisam acontecer. Alguns exemplos seriam: fechar um arquivo depois de terminar de carregar o conteúdo, liberar memória quando acabar de usar, etc.

Reduzindo o acoplamento temporal

Desde a versão 2.5, Python possui o comando with para lidar exatamente com este tipo de situação. Com ele, podemos fazer:

    with open(...) as f:
        dados = f.read()
    # processa dados aqui

Ao usar o bloco with para abrir um arquivo, o método close() é chamado por trás dos panos pelo gerenciador de contexto incondicionalmente (isto é, mesmo que ocorra alguma exceção no código de dentro do bloco).

O código equivalente seria:

    f = open(...)
    try:
        dados = f.read()
    finally;
        f.close()

Repare como o primeiro código é mais curto e mais simples que o segundo, pois há menos interdependências entre as coisas que estão acontecendo.

A ideia é não precisar lembrar de ter que escrever try/finally e chamar o close(), utilizando por trás dos panos um código que se certifique de que as coisas aconteçam na ordem esperada. A este código, chamamos de gerenciadores de contexto, ou context managers.

Implementando um gerenciador de contexto

A maneira mais simples de implementar um gerenciador de contexto Python é utilizar o decorator contextlib.contextmanager. Vejamos um exemplo:

import os
import contextlib

@contextlib.contextmanager
def roda_em_dir(dir):
    orig_dir = os.getcwd()
    os.chdir(dir)
    try:
        yield
    finally:
        os.chdir(orig_dir)

Esse gerenciador de contexto nos permite rodar um código em um diretório qualquer, e ao fim dele voltar para o diretório que estávamos antes.

A função os.getcwd() devolve o diretório atual e a função os.chdir() entra no diretório passado como argumento, e depois os.chdir() é usada novamente para voltar para o diretório original.

Veja como usá-lo:

print('Comecei no diretorio: %s' % os.getcwd())

with roda_em_dir('/etc'):
    print('Agora estou no diretorio: %s' % os.getcwd())

print('E agora, de volta no diretorio: %s' % os.getcwd())

Rodando esse código na minha máquina, a saída é:

Comecei no diretorio: /home/elias
Agora estou no diretorio: /etc
E agora, de volta no diretorio: /home/elias

A função roda_em_dir() é o que chamamos de uma corotina, pois utiliza o comando yield para “pausar” sua execução, entregando-a para o código que está dirigindo-a. Neste caso, isso é o trabalho do decorator contextlib.contextmanager, que entrega a execução para o código que está dentro do bloco with até que este termine, o que devolverá a execução para a corotina roda_em_dir(), que irá executar o código dentro do finally.

Não se preocupe se for um pouco difícil de entender como a coisa toda funciona, estamos passando por cima de alguns tópicos avançados aqui (decorators, corotinas, etc). O importante é que você se dê conta de que pode implementar um gerenciador de contexto rapidamente usando o contextlib.contextmanager com uma função que faça yield dentro de um bloco try/finally.

Vejamos um outro exemplo, desta vez vamos fazer um gerenciador de contexto que cria um arquivo temporário para utilizarmos em um código de teste, e deleta o arquivo automaticamente ao fim do bloco with:

import os
import contextlib
import tempfile

@contextlib.contextmanager
def arquivo_temp():
    _, arq = tempfile.mkstemp()
    try:
        yield arq
    finally:
        os.remove(arq)

Repare como desta vez, o comando yield não está mais sozinho, desta vez ele está enviando a variável arq que contém o nome do arquivo temporário para ser usado no with, como segue:

with arquivo_temp() as arq:
    print('Usando arquivo temporario %s' % arq)
    print('Arquivo existe? %s' % os.path.exists(arq))

print('E agora, arquivo existe? %s' % os.path.exists(arq))

Rodando na minha máquina, a saída ficou:

Usando arquivo temporario /tmp/tmp2IUF7H
Arquivo existe? True
E agora, arquivo existe? False

Note como a variável arq pode ser usada depois do with também: isto mostra que o contexto está sendo gerenciado de maneira especial, mas o espaço de nomes de variáveis ainda é o mesmo (ou seja, o comando with é mais parecido com if e for, do que com o comando def, por exemplo).

Para concluir

Bem, apesar do mecanismo por trás ser um pouquinho complicado de entender inicialmente, você pode perceber que implementar um gerenciador de contexto não é muito difícil. Você precisa usar o decorator contextlib.contextmanager em uma função geradora fazendo yield dentro de um bloco try/finally – moleza!

Você também pode implementar um gerenciador de contexto escrevendo uma classe que implemente o protocolo do comando with, que envolve basicamente implementar dois métodos especiais chamados __enter__ e __exit__ que sinalizam respectivamente entrar e sair do bloco with.

Em geral é mais conveniente utilizar o @contextlib.contextmanager, mas em alguns casos é melhor implementar os métodos. Por exemplo, caso queira compartilhar o próprio objeto gerenciador do contexto dentro do with, você pode usar return self no método __enter__.

Agora, vá refatorar aqueles códigos com acoplamento temporal e se divirta!


por Elias Dorneles em 01 de October de 2016 às 13:13

September 26, 2016

Aprenda Python

Como escrever testes rápidos

tl;dr ----- Desenvolva uma arquitetura onde as partes sejam pequenas, fáceis de serem trocadas e tenham nomes que ajudam a entender seu comportamento. O problema ---------- No artigo [Seu teste unitário precisa rodar em menos de 100ms](http://aprenda-python.blogspot.com/2016/06/seu-teste-unitario-deve-rodar-em-menos-de-100ms.html) eu citei algumas dicas para testes rodarem rápido, mas é a

por Vinicius Assef (noreply@blogger.com) em 26 de September de 2016 às 13:00

September 20, 2016

Christiano Anderson

Vamos falar de Data Science

O assunto está na moda, muitos estão falando de data science ou ciência dos dados, alguns já começaram a trabalhar de fato com isso e muita gente tem curiosidade e não sabe como começar. O assunto é amplo, exige um vasto conhecimento de matemática, programação, infraestrutura e estrutura de dados. Sim, é um mercado de trabalho crescente, muitas vagas e pouca gente qualificada.

O que é data science ou ciência de dados?

Estamos armazenando muita informação e isso aumenta quase que exponencialmente. Fazer uma compra online, publicar uma opinião nas redes sociais, postar uma foto, reclamar de um produto ou serviço, comentar algum evento ou acontecimento do momento são exemplos de informação, que também são dados. A internet das coisas (IoT) já é uma realidade e se popularizou muito, dispositivos IoT já nascem conectados e geram muita informação.

A ciência dos dados possibilita trabalhar com informações muito diferentes (uma foto, um comentário no Twitter, uma reclamação de produto, um comentário no Facebook de algum serviço, dados de um dispositivo IoT, etc), organizar essa informação, classificar e a partir dai, identificar padrões, comportamentos e até prever a probabilidade de um acontecimento futuro.

O que faz um cientista de dados?

Cientista de dados é o profissional que domina técnicas para tornar tudo isso uma realidade. O cientista de dados não é apenas um profissional técnico, ele precisa conhecer o modelo de negócio, saber fazer perguntas e utilizar essa imensidão de dados para obter respostas rápidas, confiáveis. Organizar, classificar, aplicar modelos matemáticos e criar visualizações é o papel esperado de um cientista de dados. A resposta precisa ser simples, a visualização precisa ter uma linguagem clara e fazer sentido para o modelo de negócio de cada empresa.

O que um cientista de dados precisa conhecer?

Basicamente um pouco de matemática, programação, infraestrutura, modelagem de dados e business. As tecnologias citadas são baseadas em minhas próprias experiências, são tecnologias que utilizo para conseguir fazer perguntas e identificar as respostas certas com base em cada negócio.

Conhecimento em matemática

Álgebra linear é algo fundamental na ciência de dados. O uso de matrizes, vetores e suas propriedades é algo necessário para conseguir organizar e cruzar tantas informações diferentes. Isso é rotina na vida de um cientista de dados!

Estatística e probabilidade é outro ramo da matemática que requer uma atenção especial. Estatística é um assunto amplo, o conhecimento básico é essencial, mas também um conhecimento um pouco mais aprofundado em técnicas de apresentação, gráficos, tabelas e saber fazer inferências estatísticas é um grande diferencial. Nada adianta ter um volume considerável de dados se não consegue apresentar esses dados em linguagem clara e direta. É aí que o conhecimento em estatística ajuda muito.

Linguagem de programação

Amassar os dados requer um conhecimento de programação. No meu caso, uso muito Python e um pouco de R para algumas situações. Como meu domínio maior está em Python, é nessa linguagem que concentro boa parte de meu trabalho. Vale lembrar que Python é uma das linguagens que mais crescem quando o assunto é data science. Algumas ferramentas e bibliotecas essenciais para data science:

  • Jupyter notebook: É muito produtivo quando você pode visualizar rapidamente o que está sendo feito, para isso o conceito de notebooks ajuda bastante. O Jupyter permite também ser executado em um servidores e cluster, o que é indispensável quando se trabalhar com um dataset muito grande.
  • Pandas: É uma biblioteca de Python para manusear e conhecer datasets. Quando se obtém alguma informação nova, é necessário explorar, identificar alguns padrões para a partir deste ponto decidir o que pode ser feito. Neste caso Pandas ajuda muito.
  • NumPy: A estrutura da linguagem Python já ajuda muito no trabalho com dados, mas quando o volume é muito grande, NumPy pode ser a salvação. Lembra da álgebra linear citada anteriormente? NumPy permite trabalhar com arrays, vetores, matrizes e realizar operações com muito mais performance, inclusive métodos mais avançados como transformações Fourier, etc.
  • Matplotlib: Depois de explorar o dataset, realizar algumas operações com Pandas e/ou NumPy, é necessário visualizar esses dados. Matplotlib é excelente neste sentido, uma biblioteca bastante completa, possível tanto plotar funções matemáticas quanto gráficos de N formatos diferentes. Seu uso com Jupyter notebook é sensacional. Dentro de cada célula é possível enriquecer com gráficos. Se utilizada com as bibliotecas a seguir, o resultado fica ainda mais rico;
  • Bokeh: Comecei a trabalhar há pouco tempo com o Bokeh, mas já é claro seu potencial, pois utiliza D3.JS para criação de gráficos ricos e interativos no browser.
  • Seaborn: Uma outra biblioteca que ainda estou explorando, mas que tem um grande potencial. Os gráficos são bonitos e tem um foco mais estatístico, sendo possível customizar bem cada gráfico.
  • Scikit-learn: Este é um assunto que requer um post a parte, pois trata de machine learning. Com Scikit-learn é possível realizar regressão, progressão, agrupamento (clustering) e aprendizado de máquina. É um assunto amplo e a biblioteca possui diversos algoritmos, cada um para uma finalidade específica. Quero escrever um post apenas sobre Scikit-learn (e farei em breve);
  • NLTK: Quando se trabalha com textos e conteúdo de usuários é essencial saber o que extrair de informações úteis. NLTK representa um toolkit para trabalhar com linguagem natural, isso é, classificar um texto, extrair verbos, stopwords, aproximar palavras e identificar padrões linguísticos, tudo isso é possível com NLTK.

Basicamente são essas bibliotecas/ferramentas que utilizo na linguagem Python, mas o universo de bibliotecas é muito maior.

Minha máquina de trabalho é Linux e uso a distribuição Fedora, por ter um ciclo de lançamento regular (6 meses) e possuir pacotes bem atualizados. Mas para quem utiliza Mac ou Windows, procure pela distribuição Python Anaconda, esta distribuição possui todos os pacotes e dependências que citei acima, a instalação é muito simples!

Infraestrutura

Trabalhar com datasets enormes exige infraestrutura para suportar. Domínio de Linux é muito importante, saber instalar as diversas bibliotecas acima e suas dependências, utilizar sistemas de persistência para cada caso é também muito importante. Neste quesito aproveito para citar a importância dos bancos não relacionais (NoSQL), como:

  • MongoDB: orientado a documentos (você encontra muitos artigos de MongoDB e NoSQL neste blog);
  • Neo4J: grafos;
  • HBase: paradigma de colunas;
  • Redis: Chave/Valor;
  • ArangoDB: Multimodelagem, suporta tanto documentos quanto grafos;
  • PostgreSQL: Banco de dados SQL, uma das melhores (se não for a melhor) opção SQL livre;

O cientista de dados precisa saber guardar sua informação corretamente depois que foi tratada. Não existe um banco melhor ou pior, depende muito do tipo de informação que está sendo armazenada. O MongoDB faz sentido para alguns tipos de dados, para outros, o PostgreSQL pode ser melhor. Depende de cada caso!

O diagrama abaixo resume bem o que foi explicado neste post, o ideal é começar a estudar aos poucos, resolvendo casos pontuais para conseguir se aprofundar no assunto.

data_science_venn

Fonte: http://www.oralytics.com/2012/06/data-science-is-multidisciplinary.html

Bônus: wordcloud

A imagem abaixo possui todas as palavras fortes deste post. Quanto maior a repetição, maior o tamanho da fonte. Esse recurso se chama wordcloud e utilizo nas minhas aulas como exemplo de linguagem natural. Para que seja relevante, é necessário retirar as stopwords (preposições, artigos, etc) e padronizar o texto. Esse código foi desenvolvido em Python, usando Jupyter notebook e as bibliotecas Matplotlib e NLTK. O repositório está disponível aqui.

 

Wordcloud

O post Vamos falar de Data Science apareceu primeiro em Christiano Anderson.

por Christiano Anderson em 20 de September de 2016 às 19:03

Aprenda Python

Como ter seu próprio verdadeiro e falso em Python

É possível que você já tenha visto ou escrito um trecho de código assim: if nome: print ("O nome é " + nome) O foco desse texto é a linha `if nome:`. Você já deve saber que, em Python, objetos "preenchidos" retornam `True` e objetos "vazios" retornam `False`. Mas o que são, exatamente, objetos "preenchidos" e objetos "vazios"? Dependendo do tipo do objeto, fica fácil. Seguem

por Vinicius Assef (noreply@blogger.com) em 20 de September de 2016 às 01:52

September 16, 2016

Aprenda Python

Onde salvar o código de teste?

tl;dr ----- Os testes automatizados são o primeiro cliente do código de produção. Mantenha-os separados. O problema ---------- A primeira dúvida de quem resolve escrever testes automatizados é: _"Como começar?"_. Principalmente se a abordagem for [Test First](http://c2.com/cgi/wiki?TestFirstDesign) porque muda completamente a forma de pensar o problema e a solução. Entretanto, existe

por Vinicius Assef (noreply@blogger.com) em 16 de September de 2016 às 00:56

September 01, 2016

Programando Ciência

[EVENT] Programming science on Research Data Visualization Workshop

Hey scientist! How is it going?

Today we’re going to talk about a very nice event in which we were: the Research Data Visualisation Workshop. A full day about programming, data visualization and meeting with a lot of friends! Here we go!

The Research Data Visualisation Workshop, or RDVW [1], happened on July 28 on the University of Manchester, organized by the Software Sustainability Institute [2].

The event explored data visualization and good programming and reproducibility practices in different languages. We had excellent lectures about:

  • Design Principles in Information Visualization, with Professor Jessie Kennedy [3].
  • Human Science Visualization, with Dr Martin Chorley [4].
  • Why #barbarplots?, with Dr Christina Bergmann [5].
  • Visualizing world data, with Dr Andy South [6].

We had the hands-on sessions in the afternoon, using different programming languages:

  • Python, with Dr Leighton Pritchard [7].
  • R, with Dr Andy South.
  • MATLAB, with Dr Alexandre de Siqueira (me!!!) [8].
  • Javascript, with Dr Martin Chorley.

Weren’t you there? Don’t be sad, dear scientist! All lectures are available on YouTube [9], and all codes and slides are available on Google Drive [10]. There are lots of materials for you! 🙂 Go for it!

We thank a lot the lecturers and developers, and also the RDVW organizers: Raniere Silva [11], Olivia Guest [12], Vincent Knight [13], Neil Chue Hong [14], Shoaib Sufi [15], Selina Aragon, Clementine Hadfield and Graeme Smith. What a nice event! We want more of that 🙂

Thanks scientist! Enjoy the materials! Gigaregards, see you next time!


Did you like this post? Please comment and share with your friends!
Want to download Programando Ciência codes? Go to our GitHub!
Make a donation for Programando Ciência!
Like us also on Facebook: www.facebook.com/programandociencia
I’m on Twitter! Follow me if you can! @alexdesiqueira


por alexandrejaguar em 01 de September de 2016 às 11:45

[EVENTO] Programando Ciência no Research Data Visualization Workshop

Faaaaaaaaaaaaala cientista! Tudo certo?
Hoje vamos falar de um evento super bacana do qual participamos: o Research Data Visualisation Workshop, um dia inteiro de programação, visualização de dados e confraternização com vários amigos! Vamo lá?

O Workshop de Visualização de Dados de Pesquisa (Research Data Visualisation Workshop, ou RDVW) [1] aconteceu no dia 28 de julho na Universidade de Manchester, e foi organizado pelo Software Sustainability Institute [2].

O evento explorou a visualização de dados e boas práticas de programação e reprodutibilidade em diferentes linguagens. Tivemos excelentes palestras sobre os temas a seguir:

  • Princípios de visualização de informações, com a Professora Jessie Kennedy [3].
  • Visualização em ciências humanas, com o Dr. Martin Chorley [4].
  • Visualização em biologia, com a Dra. Christina Bergmann [5].
  • Geovisualização, com o Dr. Andy South [6].

À tarde foi a hora das sessões “mão na massa”, com diferentes linguagens de programação:

  • Python, com o Dr. Leighton Pritchard [7].
  • R, com o Dr. Andy South.
  • MATLAB, com o Dr. Alexandre de Siqueira (eu!!!) [8].
  • Javascript, com o Dr. Martin Chorley.

Não pôde estar lá? Não fique triste caro cientista! Todas as palestras estão disponíveis no YouTube [9], e os códigos e slides estão disponíveis no Google Drive [10], separados por linguagem de programação. Tem muito material pra aprender coisas novas 🙂 Vai que é sua, marajá!

Agradecemos de montão aos palestrantes e desenvolvedores, e também à organização do RDVW: os bróderes e sísteres Raniere Silva [11], Olivia Guest [12], Vincent Knight [13], Neil Chue Hong [14], Shoaib Sufi [15], Selina Aragon, Clementine Hadfield e Graeme Smith. Que evento bacana! Queremos mais 🙂

Valeu cientista! Aproveite os materiais! Um giga abraço e até a próxima!


Gostou? Curta e compartilhe!
Quer baixar os códigos do Programando Ciência? Corre lá no nosso GitHub!
Faça uma doação pro Programando Ciência!
Estou no Twitter! Siga-me se puder! @alexdesiqueira

 


por alexandrejaguar em 01 de September de 2016 às 10:15

August 09, 2016

Magnun Leno

Hack ‘n’ Cast v1.0 - Stable Release

Chegou o momento de virarmos a chave e incrementarmos o major version number desse podcast.

Baixe o episódio e leia o shownotes

por Magnun em 09 de August de 2016 às 03:42

August 08, 2016

Magnun Leno

Django: Utilizando Tabelas de Outros Sistemas Parte 1/2

Recentemente tive a necessidade de fazer o Django ler 3 tabelas de um outro sistema para cruzamento de informações. Como essa necessidade pode ser a necessidade de outras pessoas, achei, por mais simples que seja, que seria útil escrever sobre o assunto.

Django

Django: Utilizando Tabelas de Outros Sistemas Parte 1/2 é um artigo original de Mind Bending

por Magnun em 08 de August de 2016 às 19:25

August 01, 2016

Gustavo Niemeyer

mgo r2016.08.01

A major new release of the mgo MongoDB driver for Go is out, including the following enhancements and fixes:


Decimal128 support

Introduces the new bson.Decimal128 type, upcoming in MongoDB 3.4 and already available in the 3.3 development releases.

Extended JSON support

Introduces support for marshaling and unmarshaling MongoDB’s extended JSON specification, which extend the syntax of JSON to include support for other BSON types and also allow for extensions such as unquoted map keys and trailing commas.

The new functionality is available via the bson.MarshalJSON and bson.UnmarshalJSON functions.

New Iter.Done method

The new Iter.Done method allows querying whether an iterator is completely done or there’s some likelyhood of more items being returned on the next call to Iter.Next.

Feature implemented by Evan Broder.

Retry on Upsert key-dup errors

Curiously, as documented the server can actually report a key conflict error on upserts. The driver will now retry a number of times on these situations.

Fix submitted by Christian Muirhead.

Switched test suite to daemontools

Support for supervisord has been removed and replaced by daemontools, as the latter is easier to support across environments.

Travis CI support

All pull requests and master branch changes now being tested on several server releases.

Initial collation support in indexes

Support for collation is being widely introduced in 3.4 release, with experimental support already visible in 3.3.

This releases introduces the Index.Collation field which may be set to a mgo.Collation value.

Removed unnecessary unmarshal when running commands

Code which marshaled the command result for debugging purposes was being run out of debug mode. This has been fixed.

Reported by John Morales.

Fixed Secondary mode over mongos

Secondary mode wasn’t behaving properly when connecting to the cluster over a mongos. This has been fixed.

Reported by Gabriel Russell.

Fixed BuildInfo.VersionAtLeast

The VersionAtLeast comparison was broken when comparing certain strings. Logic was fixed and properly tested.

Reported by John Morales.

Fixed unmarshaling of ,inline structs on 1.6

Go 1.6 changed the behavior on unexported anonymous structs.

Livio Soares submitted a fix addressing that.

Fixed Apply on results containing errmsg

The Apply method was confusing a resulting object containing an errmsg field as an actual error.

Reported by Moshe Revah.

Improved documentation

Several contributions on documentation improvements and fixes.

por niemeyer em 01 de August de 2016 às 19:57

July 26, 2016

Bruno Cezar Rocha

Microservices with Python, RabbitMQ and Nameko

"Micro-services is the new black" - Splitting the project in to independently scalable services is the currently the best option to ensure the evolution of the code. In Python there is a Framework called "Nameko" which makes it very easy and powerful.

Micro services

The term "Microservice Architecture" has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services. - M. Fowler

I recommend reading the Fowler's posts to understand the theory behind it.

Ok I so what does it mean?

In brief a Micro Service Architecture exists when your system is divided in small (single context bound) responsibilities blocks, those blocks doesn't know each other, they only have a common point of communication, generally a message queue, and does know the communication protocol and interfaces.

Give me a real-life example

The code is available on github: http://github.com/rochacbruno/nameko-example take a look at service and api folders for more info.

Consider you have an REST API, that API has an endpoint receiving some data and you need to perform some kind of computation with that data, instead of blocking the caller you can do it asynchronously, return an status "OK - Your request will be processed" to the caller and do it in a background task.

Also you want to send an email notification when the computation is finished without blocking the main computing process, so it is better to delegate the "email sending" to another service.

Scenario

enter image description here

Show me the code!

Lets create the system to understand it in practice.

Environment

We need an environment with:

  • A running RabbitMQ
  • Python VirtualEnv for services
  • Python VirtualEnv for API

Rabbit

The easiest way to have a RabbitMQ in development environment is running its official docker container, considering you have Docker installed run:

docker run -d --hostname my-rabbit --name some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management

Go to the browser and access http://localhost:15672 using credentials guest:guest if you can login to RabbitMQ dashboard it means you have it running locally for development.

enter image description here

The Service environment

Now lets create the Micro Services to consume our tasks. We'll have a service for computing and another for mail, follow the steps.

In a shell create the root project directory

$ mkdir myproject
$ cd myproject

Create and activate a virtualenv (you can also use virtualenv-wrapper)

$ virtualenv service_env
$ source service_env/bin/activate

Install nameko framework and yagmail

(service_env)$ pip install nameko
(service_env)$ pip install yagmail

The service code

Now having that virtualenv prepared (consider you can run service in a server and API in another) lets code the nameko RPC Services.

We are going to put both services in a single python module, but you can also split in separate modules and also run them in separate servers if needed.

In a file called service.py

import yagmail
from nameko.rpc import rpc, RpcProxy


class Mail(object):
    name = "mail"

    @rpc
    def send(self, to, subject, contents):
        yag = yagmail.SMTP('myname@gmail.com', 'mypassword')
        # read the above credentials from a safe place.
        # Tip: take a look at Dynaconf setting module
        yag.send(to=to.encode('utf-8'), 
                 subject=subject.encode('utf-8'), 
                 contents=[contents.encode('utf-8')])


class Compute(object):
    name = "compute"
    mail = RpcProxy('mail')    

    @rpc
    def compute(self, operation, value, other, email):
        operations = {'sum': lambda x, y: int(x) + int(y),
                      'mul': lambda x, y: int(x) * int(y),
                      'div': lambda x, y: int(x) / int(y),
                      'sub': lambda x, y: int(x) - int(y)}
        try:
            result = operations[operation](value, other)
        except Exception as e:
            self.mail.send.async(email, "An error occurred", str(e))
            raise
        else:
            self.mail.send.async(
                email, 
                "Your operation is complete!", 
                "The result is: %s" % result
            )
            return result

Now with the above services definition we need to run it as a Nameko RPC service.

NOTE: We are going to run it in a console and leave it running, but in production it is recommended to put the service to run using supervisord or an alternative.

Run the service and let it running in a shell

(service_env)$ nameko run service --broker amqp://guest:guest@localhost
starting services: mail, compute
Connected to amqp://guest:**@127.0.0.1:5672//
Connected to amqp://guest:**@127.0.0.1:5672//

Testing it

Go to another shell (with the same virtenv) and test it using nameko shell

(service_env)$ nameko shell --broker amqp://guest:guest@localhost
Nameko Python 2.7.9 (default, Apr  2 2015, 15:33:21) 
[GCC 4.9.2] shell on linux2
Broker: amqp://guest:guest@localhost
>>>

You are now in the RPC client testing shell exposing the n.rpc object, play with it

>>> n.rpc.mail.send("name@email.com", "testing", "Just testing")

The above should sent an email and we can also call compute service to test it, note that it also spawns an async mail sending with result.

>>> n.rpc.compute.compute('sum', 30, 10, "name@email.com")
40
>>> n.rpc.compute.compute('sub', 30, 10, "name@email.com")
20
>>> n.rpc.compute.compute('mul', 30, 10, "name@email.com")
300
>>> n.rpc.compute.compute('div', 30, 10, "name@email.com")
3

Calling the micro-service through the API

In a different shell (or even a different server) prepare the API environment

Create and activate a virtualenv (you can also use virtualenv-wrapper)

$ virtualenv api_env
$ source api_env/bin/activate

Install Nameko, Flask and Flasgger

(api_env)$ pip install nameko
(api_env)$ pip install flask
(api_env)$ pip install flasgger

NOTE: In api you dont need the yagmail because it is service responsability

Lets say you have the following code in a file api.py

from flask import Flask, request
from flasgger import Swagger
from nameko.standalone.rpc import ClusterRpcProxy

app = Flask(__name__)
Swagger(app)
CONFIG = {'AMQP_URI': "amqp://guest:guest@localhost"}


@app.route('/compute', methods=['POST'])
def compute():
    """
    Micro Service Based Compute and Mail API
    This API is made with Flask, Flasgger and Nameko
    ---
    parameters:
      - name: body
        in: body
        required: true
        schema:
          id: data
          properties:
            operation:
              type: string
              enum:
                - sum
                - mul
                - sub
                - div
            email:
              type: string
            value:
              type: integer
            other:
              type: integer
    responses:
      200:
        description: Please wait the calculation, you'll receive an email with results
    """
    operation = request.json.get('operation')
    value = request.json.get('value')
    other = request.json.get('other')
    email = request.json.get('email')
    msg = "Please wait the calculation, you'll receive an email with results"
    subject = "API Notification"
    with ClusterRpcProxy(CONFIG) as rpc:
        # asynchronously spawning and email notification
        rpc.mail.send.async(email, subject, msg)
        # asynchronously spawning the compute task
        result = rpc.compute.compute.async(operation, value, other, email)
        return msg, 200

app.run(debug=True)

Put the above API to run in a different shell or server

(api_env) $ python api.py 
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

and then access the url http://localhost:5000/apidocs/index.html you will see the Flasgger UI and you can interact with the api and start producing tasks on queue to the service to consume.

[image]

NOTE: You can see the shell where service is running for logging, prints and error messages. You can also access the RabbitMQ dashboard to see if there is some message in process there.

There is a lot of more advanced things you can do with Nameko framework you can find more information on https://nameko.readthedocs.org/en/stable/

Let's Micro Serve!

por Bruno Rocha em 26 de July de 2016 às 13:29

July 25, 2016

Ricbit

A Busca Por Bozo Binário

Se você trabalhar por tempo o suficiente com Computação, eventualmente uma das suas atribuições vai ser entrevistar candidatos para a sua equipe. Entrevistar é uma arte: existem inúmeros truques e macetes para descobrir se é esse sujeito na sua frente é mesmo o cara que você quer como seu parceiro. O Joel tem uma boa introdução sobre o tema que vale a pena ler.

Uma das habilidades que você precisa desenvolver rápido é aprender que o candidato não pensa como você. Em especial, quando ele responde uma pergunta de um jeito diferente daquele que você esperava, não quer dizer necessariamente que ele esteja errado. E esse caso eu já vi de perto, com o algoritmo que eu apelidei de Busca Por Bozo Binário.

Essa história aconteceu faz algum tempo. Eu estava na cantina conversando sobre entrevistas, quando alguém comentou que rejeitou um candidato porque ele não conseguiu implementar uma busca binária. Poxa, isso é grave mesmo! E qual o algoritmo errado que ele fez? A resposta foi essa:

"Sorteie um número de 1 até o tamanho do vetor, e verifique o elemento que tinha esse índice. Se for igual você achou, então retorna. Se for menor, repete para o lado esquerdo; se for maior, repete para o lado direito."

Isso é errado, me disseram, porque o pior caso é ##O(n)##. De fato, se o elemento que você quer achar é o primeiro, e o gerador de aleatórios retornar sempre o último, ele vai passar pelo vetor inteiro.

Mas nesse ponto eu tive que interromper: "Peraí! Você explicitou que queria ##O(\log n)## no pior caso? Se você não falou nada no enunciado, ele pode ter entendido que era ##O(\log n)## no caso médio, e aí o algoritmo dele está certo!"

Na hora ninguém botou fé que esse algoritmo de Busca por Bozo Binário era de fato ##O(\log n)##, então eu tive que voltar para casa e escrever a demonstração. Se você tem medo de Matemática Discreta, pule a caixa azul:

Para a Busca por Bozo Binário, nós queremos calcular qual é o valor médio do número de comparações que ele realiza. E como você começa uma análise de caso médio?

Primeiro você define as características da entrada: eu vou assumir que todos os casos estão distribuídos uniformemente. Depois, é só usar a fórmula do valor esperado: você soma o número de comparações em cada caso, multiplicado pela probabilidade daquele caso ocorrer. $$ F[x] = \sum_i p[i]C[i] $$ Antes de começar, vejamos os casos extremos. Quando o vetor é vazio, não precisa de nenhuma comparação, então ##F[0]=0##. Se o vetor tiver tamanho um, então só precisa de uma comparação, ##F[1]=1##. É sempre bom ter uns casos pequenos para conferir no final.

Vamos ver agora o caso de um vetor de tamanho ##n##. Eu não sei qual número vai ser escolhido para cortar o vetor em dois, é o gerador de números aleatórios que escolhe. Então eu vou tirar a média sobre todas as escolhas possíveis. Como os ##n## valores são equiprováveis, então a probabilidade individual é ##1/n##: $$F[n] = \sum_{0\le k\lt n}\frac{1}{n}F[k,n] = \frac{1}{n}\sum_{0\le k\lt n}F[k,n] $$ Na fórmula acima, ##F[n]## é o número médio de comparações para um vetor de tamanho ##n##, e ##F[k,n]## é o número médio de comparações para o vetor de tamanho ##n##, no caso em que o corte foi feito no elemento ##k##.

Vamos calcular ##F[k,n]## agora. Esse valor depende de onde está o número que estamos procurando. Eu não sei onde está o número; como estamos calculando o caso médio, precisamos tirar a média sobre todos as posições possíveis. $$F[k,n]=\sum_{0\le i\lt n}\frac{1}{n}F[i,k,n]=\frac{1}{n}\sum_{0\le i\lt n}F[i,k,n]$$ Agora ##F[i,k,n]## significa o valor médio para um vetor de tamanho ##n##, cortado na posição ##k##, e com número final na posição ##i##.

Nós temos três casos agora. Se ##i=k##, então você só precisa comparar uma vez, a comparação vai ser verdadeira e o algoritmo termina. Se ##i\lt k##, então você compara uma vez e faz recursão para o lado esquerdo. Se ##i\gt k##, então você compara uma vez e faz recursão para o lado direito. Resumindo: $$F[i,k,n]=\begin{cases} 1+F[k] &(i \lt k) \\ 1 &(i = k) \\ 1+F[n-k-1] &(i \gt k) \\ \end{cases}$$ Podemos sintetizar tudo que vimos em uma única recorrência. $$\begin{align*} F[0] &= 0 \\ F[n] &= \frac{1}{n} \sum_{0\le k \lt n}\left( \frac{1}{n}\sum_{0\le i\lt k}\left(1+ F[k]\right) + \frac{1}{n} + \frac{1}{n}\sum_{k\lt i\lt n}\left(1+ F[n-k-1]\right) \right) \end{align*}$$ Agora é só resolver!

Antes de mais nada, vamos coletar quem é constante em relação ao índice da somatória. Todos os ##1/n## coletam, e as somatórias mais internas viram constantes. $$\begin{align*} F[n] &= \frac{1}{n^2} \sum_{0\le k \lt n}\left( \sum_{0\le i\lt k}\left(1+ F[k]\right) + 1 + \sum_{k\lt i\lt n}\left(1+ F[n-k-1]\right) \right) \\ &= \frac{1}{n^2} \sum_{0\le k \lt n} k \left(1+ F[k]\right) + 1 + \left(n-k-1\right)\left(1+ F[n-k-1]\right) \\ &= \frac{1}{n^2} \sum_{0\le k \lt n} k + k F[k] + 1 + n-k-1 +\left(n-k-1\right)F[n-k-1] \\ &= \frac{1}{n^2} \sum_{0\le k \lt n} n + k F[k] +\left(n-k-1\right)F[n-k-1] \\ &= 1+\frac{1}{n^2} \left(\sum_{0\le k \lt n} k F[k] +\sum_{0\le k \lt n} \left(n-k-1\right)F[n-k-1] \right) \\ \end{align*}$$ Vamos focar naquela segunda somatória. Primeiro fazemos ##q=n-k-1##: $$ \sum_{0\le k \lt n} \left(n-k-1\right)F[n-k-1] =\sum_{0\le n-q-1 \lt n} qF[q] $$ Agora é só manipular os índices: $$\begin{align*} 0\le n-&q-1 \lt n \\ 1-n\le -&q \lt 1 \\ -1\lt &q \le n-1 \\ 0\le &q \lt n \\ \end{align*}$$ Olha só, a segunda somatória é igual à primeira! $$ \sum_{0\le n-q-1 \lt n} qF[q] =\sum_{0\le q \lt n} qF[q] =\sum_{0\le k \lt n} kF[k] $$ Substituindo, chegamos em uma fórmula curta para ##F[n]##: $$\begin{align*} F[n] &= 1+\frac{1}{n^2} \left(\sum_{0\le k \lt n} k F[k] +\sum_{0\le k \lt n} \left(n-k-1\right)F[n-k-1] \right) \\ &= 1+\frac{1}{n^2} \left(\sum_{0\le k \lt n} k F[k] +\sum_{0\le k \lt n} kF[k] \right) \\ &= 1+\frac{2}{n^2} \sum_{0\le k \lt n} k F[k] \\ \end{align*}$$ A somatória ainda está atrapalhando, o ideal seria sumir com ela. Uma maneira de fazer isso é isolando: $$\begin{align*} F[n] &= 1+\frac{2}{n^2} \sum_{0\le k \lt n} k F[k] \\ \sum_{0\le k \lt n} k F[k] &= \frac{n^2}{2}\left(F[n]-1\right) \end{align*}$$ Essa última fórmula vale para todo ##n##, em especial vale também para ##n-1##: $$\begin{align*} \sum_{0\le k \lt n} k F[k] &= \frac{n^2}{2}\left(F[n]-1\right) \\ \sum_{0\le k \lt n-1} k F[k] &= \frac{(n-1)^2}{2}\left(F[n-1]-1\right) \\ \end{align*}$$ Ahá! Agora é só subtrair uma da outra que a somatória desaparece! $$\begin{align*} \sum_{0\le k \lt n} k F[k] - \sum_{0\le k \lt n-1} k F[k] &= \frac{n^2}{2}\left(F[n]-1\right) - \frac{(n-1)^2}{2}\left(F[n-1]-1\right)\\ (n-1) F[n-1] &= \frac{n^2}{2}\left(F[n]-1\right) - \frac{(n-1)^2}{2}\left(F[n-1]-1\right)\\ (n-1) F[n-1] &= \frac{n^2}{2}F[n]-\frac{n^2}{2} - \frac{(n-1)^2}{2}F[n-1]+\frac{(n-1)^2}{2}\\ \frac{n^2}{2}F[n] &= \left(\frac{(n-1)^2}{2}+(n-1)\right)F[n-1]+\left(\frac{n^2}{2} -\frac{(n-1)^2}{2}\right)\\ n^2F[n] &= \left(n^2-1\right)F[n-1]+(2n-1)\\ \end{align*}$$ Chegamos finalmente em uma recorrência sem somatórias. Melhor ainda, essa recorrência está no ponto certo para usar a técnica do summation factor! Como esse é um truque muito útil de se conhecer, eu vou fazer em câmera lenta. Você pode usar um summation factor sempre que sua recorrência for da forma abaixo: $$a_n F[n] = b_n F[n-1]+c_n$$ Esse é o nosso caso, se usarmos a correspondência abaixo: $$\begin{align*} a_n &= n^2 \\ b_n &= n^2-1 \\ c_n &= 2n-1 \end{align*}$$ O segredo do summation faction é tentar achar uma sequência mágica ##s_n## que tenha a seguinte propriedade: $$s_n b_n=s_{n-1}a_{n-1}$$ E por que isso é bom? Olha só o que acontece quando você multiplica a recorrência original por ##s_n##: $$\begin{align*} a_n F[n] &= b_n F[n-1]+c_n \\ s_n a_n F[n] &= s_n b_n F[n-1]+s_n c_n \\ s_n a_n F[n] &= s_{n-1}a_{n-1}F[n-1]+s_n c_n \\ \end{align*}$$ Agora, se você substituir ##T[n]=s_n a_n F[n]##, temos: $$\begin{align*} s_n a_n F[n] &= s_{n-1}a_{n-1}F[n-1]+s_n c_n \\ T[n] &= T[n-1]+s_n c_n \end{align*}$$ Opa, essa é fácil! Dá pra resolver de cabeça, é praticamente a definição de somatória! $$\begin{align*} T[n] &= T[n-1]+s_n c_n\\ T[n] &= T[0] + \sum_{1\le k \le n} s_k c_k \\ s_n a_n F[n] &= s_0 a_0 F[0] + \sum_{1\le k \le n} s_k c_k \\ F[n] &= \frac{1}{s_n a_n} \left(s_0 a_0 F[0] + \sum_{1\le k \le n} s_k c_k \right) \\ \end{align*}$$ Pronto, agora não é mais uma recorrência, é uma fórmula simples. A dificuldade do método é encontrar o tal do ##s_n##. Para achá-lo você pode usar a intuição, chutar, fazer um sacrifício aos deuses; ou então você pode usar o método do porradão. Nesse método você começa com ##s_n=s_{n-1}\left(a_{n-1}/b_n\right)## e abre recursivamente, chegando no monstrinho abaixo: $$s_n = \frac{a_{n-1}a_{n-2}\ldots a_2 a_1}{b_n b_{n-1}\ldots b_3 b_2}$$ Essa fórmula parece grande, e é grande mesmo. Mas na maioria das vezes tem um monte de cancelamentos, o que deixa o resultado final pequeno. Vamos ver no nosso caso como fica: $$\begin{align*} s_n &= \frac{a_{n-1}a_{n-2}\ldots a_2 a_1}{b_n b_{n-1}\ldots b_3 b_2} \\ &= \frac{(n-1)^2 (n-2)^2 \ldots 2^2 1^2} {\left(n^2-1\right) \left(\left(n-1\right)^2-1\right) \ldots (3^2-1) (2^2-1)} \\ &= \frac{(n-1)^2 (n-2)^2 \ldots 2^2 1^2} {\left((n+1)(n-1)\right) \left((n-1+1)(n-1-1)\right) \ldots (3+1)(3-1) (2+1)(2-1)} \\ &= \frac{(n-1) (n-2)(n-3) \ldots3\cdot 2\cdot 1} {(n+1) (n) (n-1)\ldots (4+1) (3+1) (2+1)} \\ &= \frac{2} {(n+1) (n) } \\ \end{align*} $$ Cancelou mesmo! Bastou lembrar que ##(a^2-b^2)=(a+b)(a-b)## que o resto saiu. Agora é só lembrar que ##F[0]=0## e substituir na fórmula: $$\begin{align*} F[n] &= \frac{1}{s_n a_n} \left(s_0 a_0 F[0] + \sum_{1\le k \le n} s_k c_k \right) \\ F[n] &= \frac{n(n+1)}{2n^2} \sum_{1\le k \le n} \frac{2(2k-1)}{k(k+1)}\\ \end{align*}$$ Essa é a solução da recorrência. Dá para achar uma forma fechada? Nesse caso dá, desde que você tope uma forma fechada em função dos números harmônicos ##H[n]##, que são definidos como: $$H[n]=\sum_{1\le k \le n}\frac{1}{k}$$ Você começa abrindo a fração no somatório: $$\begin{align*} \frac{2(2k-1)}{k(k+1)} &= \frac{A}{k} + \frac{B}{k+1}\\ \frac{4k-2}{k(k+1)} &= \frac{A(k+1)+B k}{k(k+1)}\\ 4k-2 &= Ak+A +B k \\ 4k-2 &= (A+B)k+A \\ A &= -2 \\ B &= 6 \end{align*}$$ Agora você divide o somatório em dois: $$\begin{align*} F[n] &= \frac{n(n+1)}{2n^2} \sum_{1\le k \le n} \frac{2(2k-1)}{k(k+1)}\\ F[n] &= \frac{n(n+1)}{2n^2} \sum_{1\le k \le n} \frac{6}{k+1} - \frac{2}{k}\\ F[n] &= \frac{n(n+1)}{2n^2} \left( \sum_{1\le k \le n} \frac{6}{k+1} - \sum_{1\le k \le n} \frac{2}{k} \right) \\ F[n] &= \frac{n(n+1)}{2n^2} \left( \sum_{1\le k \le n} \frac{6}{k+1} - 2\sum_{1\le k \le n} \frac{1}{k} \right) \\ F[n] &= \frac{n(n+1)}{2n^2} \left( \sum_{1\le k \le n} \frac{6}{k+1} - 2H[n] \right)\\ \end{align*}$$ O primeiro somatório precisa de um pouco de massagem: $$\begin{align*} \sum_{1\le k \le n} \frac{6}{k+1} &= \sum_{1\le q-1 \le n} \frac{6}{q} \\ &= \sum_{2\le q \le n+1} \frac{6}{q} \\ &= -6+\frac{6}{n+1}+6\sum_{1\le q \le n} \frac{1}{q} \\ &= -6+\frac{6}{n+1}+6H[n] \\ \end{align*}$$ Por fim: $$\begin{align*} F[n] &= \frac{n(n+1)}{2n^2} \left( \sum_{1\le k \le n} \frac{6}{k+1} - 2H[n] \right)\\ F[n] &= \frac{n(n+1)}{2n^2} \left( -6+\frac{6}{n+1}+6H[n] - 2H[n] \right)\\ F[n] &= -3 +\frac{2(n+1)}{n}H[n]\\ \end{align*}$$ Ufa, deu trabalho! E isso é ##O(\log n)## mesmo? Bem, podemos verificar usando um assintótico para o harmônico, por exemplo: $$ H[n] = \ln n + \gamma + O(1/n) $$ Agora você substitui: $$\begin{align*} F[n] &= -3 +\frac{2(n+1)}{n}H[n]\\ &= -3 +2H[n] +\frac{H[n]}{n} \\ &= -3 +2\ln n +2\gamma +O(1/n)+ 2\frac{\ln n}{n}+2\frac{\gamma}{n}+O(1/n^2) \\ &= 2\ln n + O\left(\frac{\ln n}{n}\right) \\ &= O(\log n) \end{align*}$$ Como queríamos demonstrar!

Ou seja, realmente a Busca por Bozo Binário é ##O(\log n)##. Em demonstrações grandes assim eu sempre gosto de fazer uma simulação por Monte Carlo para conferir as contas. A minha simulação está no github:

Busca por Bozo Binário, simulação por Monte Carlo

Simulando 50000 vezes em um vetor de tamanho 3000, o número médio de comparações foi de ##14.1769##. O valor teórico previsto pela fórmula é de ##14.1732##, nada mau!

Eu também fiz uma simulação da busca binária tradicional como comparação. Com os mesmos parâmetros, a busca tradicional usou ##10.6356## comparações no caso médio, ou seja, foi mais eficiente que o Bozo Binário.

Por que isso acontece? Analisando o assintótico fica claro. Para ##n## grande, o valor médio da Busca por Bozo é de ##2 \ln n## (logaritmo natural), enquanto que na busca binária é ##\log_2 n## (logaritmo de base 2). Então, em média, esperamos que Bozo Binário seja mais lento por um fator de:

$$\begin{align*} \frac{2\ln n}{\log_2 n} &= {2\frac{\log n}{\log e}} \div {\frac{\log n}{\log 2}} \\ &= 2\frac{\log n}{\log e} \times \frac{\log 2}{\log n} \\ &= 2\frac{\log 2}{\log e} \\ &= 2\ln 2 \\ &\simeq 1.386 \end{align*}$$ Ou seja, mais ou menos uns 38% mais lento, o que bate com a simulação.

Depois da análise do algoritmo, a dúvida que resta é: e o candidato? Bem, se fosse eu a entrevistar, precisaria coletar mais dados para saber se ele realmente sabia do tempo médio, ou se estava perdido e implementou a primeira coisa que veio na cabeça.

Um possível follow-up é "Certo, esse método é mais complicado que a busca binária tradicional, mas funciona. Você pode me falar alguma situação onde ele é preferível sobre o tradicional?". Curiosamente, existe sim um caso onde a Busca por Bozo Binário é melhor que a busca binária tradicional! Mas esse caso fica para a próxima :)

por Ricardo Bittencourt (noreply@blogger.com) em 25 de July de 2016 às 14:14

July 18, 2016

Flavio Ribeiro

Snickers: Open Source HTTP API for Media Encoding

At the beginning of this year we created a group on our video engineering team to deal with the ingesting, encoding, publishing and syndication of The New York Times videos. The main goal of the team was to build a pipeline that is vendor agnostic, cloud-based, efficient and elastic. We also wanted to build an easier workflow for video producers in the newsroom and third-party partners who distribute our videos. We named this team Media Factory.

After a few months of development, the pipeline is almost ready to be deployed to production. It consists of three different steps: Content Acquisition, Transcoding and Distribution. The content acquisition part is responsible for receiving ProRes/Raw videos, the transcoding API transforms the source files in playable videos by our supported browsers and devices and the distribution step sends the final videos to our CDN.

Probably the most important and complex part of the pipeline is the transcoding process. Instead of using heavy and expensive hardware for that we decided to create clients for different encoding services like Encoding.com, Elastic Transcoder and Elemental. This enabled us to decide which one to use based on a set of variables such as availability, speed, formats supported (HLS, webm, mp4, DASH, etc) and even cost.

While discussing the design of the pipeline and the integration with cloud services, we thought it would be useful (and FUN!) to implement another provider that would be just a tiny wrapper for FFmpeg or GStreamer. By using this tiny wrapper, we would have the freedom to deploy anywhere, implement new features (such as the generation of thumbnails) and perhaps, one day, be used to encode our lower priority jobs.

We have plans to write a blog post about the details of each step and how they work together on the Open Blog. We should work on that as soon as we have people at the company using the pipeline.

Time To Learn Some Go

After having a lot of fun on the journey to remove Flash from our player I got attracted by the chance to be part of this team. I was already willing to work with back-end services again and knowing that the company is adopting Go for API's turned this opportunity into something even more attractive. Although I was participating on meetings and design decisions, I actually started working on the team in April.

I had never worked with Go before and after a few years plunged on dynamically typed languages I felt a bit of pain during the first steps working with it. The rest of the team was already fluent and I though I'd need something to exercise myself and keep up with the team's pace.

snickers logo

Creating this dummy provider that is a wrapper for FFmpeg seemed like a good chance for me to evolve my Go skills. A month ago I started writting it, I called the project Snickers. I put this name because it's my favorite chocolate and I was holding one when I created the GitHub repository.

To be honest, the title of this post doesn't actually represent the current state of the project. Right now, it looks much more like a proof-of-concept/embryo project than anything else.

It supports the download of sources from HTTP and Amazon S3, encode them in H264/MP4 or VP8/WebM using a FFmpeg binding and uploads the results to Amazon S3. We still need a plethora of features to be able to actually call it a Media Encoding API, but one of the purposes of this post is to broadcast the idea and try to engage people to collaborate. Snickers already has contributors!

Next Steps

I have a bunch of ideas for this project. The next steps include building clients in JavaScript so people can use Snickers directly on the browser (with nice progress bars and stuff) as well as a client in Go, probably as part of the encoding-wrapper project. We also need to write some glue code for the Transcoding API so Snickers can be considered in the decision process of Media Factory pipeline. We use a lot of HLS at the company so HLS support is mandatory and probably the highest priority feature.

I'm also interested in working on the creation of a GStreamer driver for the encoding part so we can have benchmarks using different drivers. I saw that there's already a Gstreamer binding for Go and it seems that the code is simpler than handling libav calls.

You can find some instructions on how to deploy and use the API as well as some examples. I also labeled a bunch of relatively easy issues in case you want to work on and be part of this project.

por Flávio Ribeiro em 18 de July de 2016 às 17:39

Aprenda Python

Conferências Django 2016

A DjangoCon está acontecendo e por isso ainda não temos os links. Assim que eles forem publicados, incluirei aqui.

por Vinicius Assef (noreply@blogger.com) em 18 de July de 2016 às 14:51