Planeta PythonBrasil

PythonBrasil[10]

November 21, 2017

Lauro Moura

C#, COM, OLE e threads

Seguindo as aventuras no mundo dos bindings C# para a EFL, um problema que enfrentei semana passada durante o port dos bindings para o Windows envolvia a famigerada API Win32. Tudo estava correndo bem até tentar mostrar algo na tela, quando os módulos de UI simplesmente se recusavam a carregar, ao contrário de quando tentávamos rodar apenas o código nativo, sem o binding. O culpado: OleInitialize.

A EFL no Windows utiliza por baixo dos panos a Win32, API tradicional para apps nativas do sistema operacional. Entre os componentes utilizados, está o velho OLE (Object Linking and Embedding), responsável por compartilhar itens entre aplicações, como por exemplo os serviços de drag and drop e clipboard, desde o começo dos anos 90. Ao longo do tempo, o OLE acabou gerando o COM (Component Object Model), que serve comunicação interprocessos para outras tecnologias do Windows – numa analogia meio “grosseira”, seria uma espécie de “DBus do Windows”, porém pelo menos 10 anos mais velho.

Para lidar com threading, o COM agrupa os objetos em apartments, que podem ser single thread – todas as chamadas a um objeto saem da mesma thread – e multi thread – cada objeto cuida de sua própria sincronização e chamadas podem vir de múltiplas threads. Entre outras coisas, o Oleinitialize cuida de inicializar o COM caso este já não esteja inicializado, e obrigatoriamente no modo single-thread. Caso a função retorne RPC_E_CHANGED_MODE, significa que o COM já foi inicializado anteriormente em modo multi-thread. E era exatamente isso que estava acontecendo.

Investigando mais um pouco, o principal suspeito era o próprio runtime do .NET. Verificando o número de threads com System.Diagnostics.Process.GetCurrentProcess().Threads.Count, um simples “hello world” indicava 6 threads em execução. E ao inspecionar o código do Mono, realmente o COM era inicializado em modo multithread. E a resposta é dada na documentação do atributo STAThreadAttribute (livre tradução):

A partir da versão 2.0 do framework .NET, o modelo de threading padrão da interoperabilidade COM depende da linguagem em que se está desenvolvendo a aplicação:

  • C++/C#: Multi-thread
  • Visual Basic: Single thread.

Prevendo esse tipo de problema, .NET oferece para C# o já mencionado atributo STAThreadAttribute, que obrigatoriamente deve decorar o Main() da aplicação para indicar que a interoperabilidade COM deve excepcionalmente ser inicializada como single thread. Para aplicações C++, a flag /CLRTHREADATTRIBUTE:STA deve ser fornecida para o linker.

o/


por lauro em 21 de November de 2017 às 19:10

November 20, 2017

PythonClub

Programação funcional com Python #0- saindo da zona de conforto

0. Saindo da zona de conforto

Sinta-se um vencedor, se você chegou até aqui, isso significa que quer aprender mais sobre o mundo da programação.

Aprender novos paradígmas podem te trazer muitas coisas positivas, assim como aprender linguagens diferentes, pois paradígmas e linguagens transpõem maneiras, estruturas e métodos de implementação completamente diferentes. Com isso você pode ter mais ferramentas para usar no dia a dia. Você pode aumentar sua capacidade de expressar ideias de diferentes maneiras. Eu penso que o maior limitador de um programador é a linguagem de programação em que ele tem domínio. Quando você aprende linguagens imperativas, como C, Python, Java e etc..., você se vê limitado ao escopo de criar e manipular variáveis. Não que isso seja uma coisa ruim, porém existem outras maneiras de resolver problemas e quando você tem conhecimento disso consegue avaliar melhor quando implementar cada tipo de coisa.

Você pode me dizer que aprender diferentes tipos de estruturas e maneiras de computar é uma coisa negativa pois tudo é variável nesse contexto. Mas eu penso exatamente o contrário, quanto mais você aprender da sua língua nativa, no caso estamos falando em português, maior o campo de domínio que você tem sobre como se comunicar e expressar ideias. Assim como aprender outras línguas te darão mais fundamentos para expressar ideias em outros idiomas, que não são melhores que os seu, mas diferentes e compõem diferentes estruturas, e isso pode ser libertador. Não quero me prolongar nesse assunto, mas dizer que isso pode acrescentar muito na suas habilidades cognitivas, até mesmo para usar ferramentas que você já usa no seu dia a dia.

Vamos começar fazendo uma tentativa de entender os paradígmas de programação, sem muito falatório e complicações. Um exemplo muito legal é do David Mertz em "Functional Programming in Python":

  • Usa-se programação funcional quando se programa em Lisp, Haskell, Scala, Erlang, F# etc..

  • Do mesmo modo que se usa programação imperativa quando se programada C/C++, Pascal, Java, Python etc...

  • Também quando se programa Prolog estamos programando usando o paradígma lógico.

Apesar de não ser uma definição muito elegante, talvez seja a melhor a ser dada em muitas ocasiões. Vamos tentar ser um pouco mais objetivos em relação ao estilo de computação, embora essa discussão não tenha fim:

  • O foco de usar programação imperativa está no ato de mudar variáveis. A computação se dá pela modificação dos estados das variáveis iniciais. Sendo assim, vamos pensar que tudo é definido no início e vai se modificando até que o resultado esperado seja obtido.

  • Na programação funcional, se tem a noção de que o estado deve ser substituído, no caso da avaliação, para criação de um novo 'objeto' que no caso são funções.

0.1 Mas de onde vem a programação funcional?

O florescer da programação funcional nasce no Lisp (acrônomo para List Processing) para tentar resolver alguns problemas de inteligência artificial que eram provenientes da linguística, que tinha foco em processamento de linguagem natural que por sua vez eram focados em processamento de listas em geral. Isso justifica uma grande parte do conteúdo que vamos ver aqui e seus tipos de dados variam somente entre listas e átomos. E assim foi mantido o foco de processamento de listas em todas as linguagens funcionais e suas funções e abstrações para resolver problemas relativos a listas e estruturas iteráveis. Uma curiosidade é que para quem não sabe porque em lisp existem tantos parênteses é que ele é baseado em s-expression, uma coisa que temos um "equivalente" evoluído em python, que parte dos teoremas de gramáticas livres de contexto:

(+ 4 5)

Sim, isso é uma soma em lisp. Diferente das linguagens imperativas como costumamos ver:

4 + 5

Uma assertiva pode ser feita dessa maneira:

  • Funcional (ou declarativa)
(= 4 (+ 2 2))
  • Imperativa
(2 + 2) == 4

Chega de enrolação e vamos correr com essa introdução, não viemos aqui para aprender Lisp ou C. Mas acho que parte desse contexto pode nos ajudar e muito quando formos nos aprofundar em alguns tópicos. Pretendo sempre que iniciar uma nova ferramenta da programação funcional ao menos explicar em que contexto ela foi desenvolvida e para resolver cada tipo de problema.

0.2 Técnicas usadas por linguagens funcionais

Vamos tentar mapear o que as linguagens funcionais fazem de diferente das linguagens imperativas, mas não vamos nos aprofundar nesse tópicos agora, pois são coisas às vezes complexas sem o entendimento prévio de outros contextos, mas vamos tentar só explanar pra que você se sinta empolgado por estar aqui:

  • Funções como objetos de primeira classe:

    • São funções que podem estar em qualquer lugar (em estruturas, declaradas em tempo de execução).
  • Funções de ordem superior:

    • São funções que podem receber funções como argumentos e retornar funções.
  • Funções puras:

    • São funções que não sofrem interferências de meios externos (variáveis de fora). Evita efeitos colaterais.
  • Recursão, como oposição aos loops:

    • Frequentemente a recursão na matemática é uma coisa mais intuitiva e é só chamar tudo outra vez, no lugar de ficar voltando ao ponto inicial da iteração.
  • Foco em processamento de iteráveis:

    • Como dito anteriormente, pensar em como as sequências podem nos ajudar a resolver problemas.
  • O que deve ser computado, não como computar:

    • Não ser tão expressivo e aceitar que as intruções não tem necessidade de estar explicitas todas as vezes, isso ajuda em legibilidade.
  • Lazy evaluation:

    • Criar sequências infinitas sem estourar nossa memória.

0.3 Python é uma linguagem funcional?

Não. Mas é uma linguagem que implementa muitos paradígmas e porque não usar todos de uma vez?

O objetivo desse 'conjunto de vídeos' é escrever código que gere menos efeito colateral e código com menos estados. Só que isso tudo feito na medida do possível, pois Python não é uma linguagem funcional. Porém, podemos contar o máximo possível com as features presentes do paradígma em python.

Exemplos de funcional (básicos) em python:

# Gerar uma lista da string # Imperativo
string = 'Python'
lista = [] # estado inicial

for l in string:
    lista.append(l) # cada iteração gera um novo estado

print(lista) # ['P', 'y', 't', 'h', 'o', 'n']
# Gerar uma lista da string # Funcional
string = lambda x: x

lista = list(map(str, string('Python'))) # atribuição a um novo objeto

print(lista) # ['P', 'y', 't', 'h', 'o', 'n']

Como você pode ver, depois de uma explanação básica das técnicas, a segunda implementação não sofre interferência do meio externo (Funções puras), evita loops e sua saída sem o construtor de list é lazy. Mas não se assuste, vamos abordar tudo isso com calma.

0.4 A quem esse 'curso' é destinado?

Primeiramente gostaria de dizer que roubei essa ideia dos infinitos livros da O’Reilly, que sempre exibem esse tópico. Mas vamos ao assunto. Este curso é para você que sabe o básico de Python, e quando digo básico quero dizer que consegue fazer qualquer coisa com um pouco de pesquisa na internet. O básico de programação se reduz a isso. Vamos falar sobre coisas simples e coisas mais complexas, mas pretendo manter o bom senso para que todos possam absorver o máximo de conteúdo possível.

Então, caso você venha do Python (OO ou procedural) você vai encontrar aqui uma introdução a programação funcional descontraída e sem uma tonelada de material difícil de entender. Caso você venha de linguagens funcionais como Haskell e Lisp, você pode se sentir um pouco estranho com tantas declarações, mas aprenderá a se expressar em Python. Caso você venha de linguagens funcionais modernas como Clojure e Scala, as coisas são bem parecidas por aqui.

Então tente tirar o máximo de proveito. Vamos nos divertir.

0.5 Apresentando o Jaber

Jaber é nosso aluno de mentira, mas vamos pensar que ele é um aluno que senta na primeira fileira e pergunta de tudo, sempre que acha necessário. Roubei essa ideia do livro de expressões regulares do Aurélio. Ele tem um personagem, Piazinho, e acho que toda interação com ele é sempre graciosa e tira dúvidas quando tudo parece impossível.

0.6 Sobre as referências

Não gosto muito de citar referências pois procurei não copiar texto dos livros, mas muita coisa contida neles serve de base para o entendimento de certos tópicos. Outro motivo é o nível de complexidade dos exemplos ou explicações que tentei reduzir ao máximo enquanto escrevia esses roteiros. Para um exemplo, você pode olhar o livro do Steven Lott, cheio de fórmulas e abstrações matemáticas que em certo ponto acabam comprometendo o entendimento de quem não tem uma sólida base em computação teórica ou matemática.

Como um todo, as referências serviram como guia, foi o que lí quando dúvidas para explicações surgiram. Não tiro nenhum crédito delas e as exponho para que todos saibam que existem muitos livros bons e que boa parte do que é passado aqui, foi aprendido neles.

0.7 Mais sobre o histórico das linguagens funcionais

Se você pretende realmente se aprofundar no assunto enquanto acompanha esse curso, fazer uma imersão ou coisa parecida. Tudo começa com o cálculo lambda mentalizado pelo incrível Alonzo Church. Caso você não o conheça, ele foi um matemático fantástico e teve uma carreira acadêmica brilhante. Foi o orientador de pessoas incríveis como Alan Turing, Raymond Smullyan etc...

Outro grande homem e que vale a pena mencionar e ser buscado é o Haskell Curry, um lógico que trouxe excelentes contribuições para o que chamamos hoje de programação funcional.

A primeira linguagem funcional 'oficial' (não gosto muito de dizer isso) é o Lisp (List Processing) criada pelo fenomenal John McCarthy que também vale a pena ser pesquisado e estudado.

Veremos o básico sobre os tipos de função no próximo tópico.

por Eduardo Mendes em 20 de November de 2017 às 21:43

October 31, 2017

Thiago Avelino

Fico muito feliz por ter escolhido o pREST para fazer sua primeira contribuição.

Fico muito feliz por ter escolhido o pREST para fazer sua primeira contribuição.

Realmente não existe uma fórmula mágica (how to) de como contribuir com projetos open source, vai de projeto para projeto, a dica que você deu de se envolver com as pessoas que mantêm e/ou contribui é ótima.

Parabéns pelo blogpost.

por Avelino em 31 de October de 2017 às 23:48

Python Help

Como autenticar um spider Scrapy em um website

Obs.: ainda não conhece o Scrapy? Então leia este tutorial.

Vez por outra os sistemas que a gente usa não entregam as informações da forma que desejamos. Seja o sistema do seu cartão de crédito que não lhe dá uma visualização legal dos seus gastos, ou até mesmo seu app de táxi que não lhe deixa fazer uma análise mais aprofundada dos trajetos que você tem feito.

Ah se todos eles tivessem um botãozinho “exportar tudo em JSON”! 🙂

A realidade é dura e a maioria dos apps não nos dão essa opção. Uma alternativa nesses casos é raspar os dados que desejamos do site do tal app, e isso envolve fazer seu web spider se autenticar no sistema. Nesse post, vamos ver como fazer para que um spider Scrapy faça login em um sistema qualquer.

Como funciona a autenticação em um website?

Existem várias maneiras diferentes, mas em linhas gerais a autenticação em websites funciona assim:

  1. O usuário acessa URL que contém formulário de login. Exemplo: http://quotes.toscrape.com/login
  2. O usuário preenche campos do formulário com suas credenciais e clica no botão de login
  3. O navegador envia os dados preenchidos para o servidor, no corpo de uma requisição HTTP do tipo POST
  4. O servidor valida as credenciais do usuário e envia de volta uma resposta contendo uma página indicando que o usuário se autenticou com sucesso e, juntamente com ela, um cookie de sessão.

O cookie de sessão é uma informação que o browser irá armazenar localmente e irá enviar juntamente com as requisições subsequentes ao mesmo website, de forma que o último possa identificar quem é o usuário fazendo tais requisições.

Assim sendo, nosso spider deverá reproduzir os passos recém descritos para que consiga se autenticar em um website. Então vamos a ele!

Nosso spider

Vamos construir um spider que se autentique em http://quotes.toscrape.com e de lá extraia o link para a página no Goodreads de cada autor, informação que só é visível para usuários autenticados.

Dê uma olhada no esqueleto do nosso spider:

# -*- coding:utf-8 -*-
import scrapy

class LoginSpider(scrapy.Spider):
    name = 'login-spider'
    start_urls = ['http://quotes.toscrape.com/login']

    def parse(self, response):
        # depois a gente vai implementar aqui o preenchimento do formulário
        # contido em response e fazer a requisição de login
        self.log('visitei a página de login: {}'.format(response.url))

    def parse_author_links(self, response):
        # aqui a gente vai extrair os links para as páginas dos autores
        # ou seja, quando chegar aqui, o spider já estará autenticado
        pass

Quando rodarmos nosso spider, o Scrapy vai chamar automaticamente o método parse() assim que a resposta para a URL http://quotes.toscrape.com/login tiver sido baixada e a mensagem de log será impressa. Vá em frente e rode o spider acima só pra ver isso acontecendo:

$ scrapy runspider loginspider.py

Manipulando o formulário

O formulário de login do nosso site está representado assim:





<form action="/login" method="post" accept-charset="utf-8" >
    <input type="hidden" name="csrf_token" value="fHQgXTCzxs...aOkolIudtjV"/>
    <label for="username">Username</label>
    <input type="text" class="form-control" id="username" name="username" />
    <label for="username">Password</label>
    <input type="password" class="form-control" id="password" name="password" />
    <input type="submit" value="Login" class="btn btn-primary" />
</form>




Perceba que temos 4 inputs nesse formulário: username, password, o botão de login em si e um campo oculto chamado csrf_token. Não vou entrar em detalhes sobre proteção CSRF, mas o que você precisa saber é que a maioria dos sites por aí vai ter um campo desses no formulário de login deles e que você precisa enviar tal campo juntamente com a requisição de autenticação. Caso contrário, o servidor não irá validar sua requisição.

No Scrapy, requisições do tipo POST podem ser feitas com um tipo especial de Request chamado FormRequest. Nossa requisição de autenticação será:

req = scrapy.FormRequest(
    url='http://quotes.toscrape.com/login',
    formdata={
        'username': 'john.doe',
        'password': 'anything',
        'csrf_token': token,
    },
    callback=self.parse_author_links,
}

Alguns detalhes importantes:

  • O valor do campo csrf_token é gerado dinamicamente para cada requisição à página de login. Assim, teremos que extrair ele da página de login antes de fazer a requisição.
  • A URL passada ao objeto FormRequest deve ser aquela encontrada no atributo action do formulário de login.
  • As chaves do dicionário passado ao parâmetro formdata devem usar como nome o valor do atributo name do input correspondente no formulário.

Agora que a gente já sabe como fazer uma requisição do tipo POST, vamos ao spider:

# -*- coding:utf-8 -*-
import scrapy
from scrapy.exceptions import CloseSpider

class LoginSpider(scrapy.Spider):
    name = 'login-spider'
    start_urls = ['http://quotes.toscrape.com/login']

    def parse(self, response):
        self.log('visitei a página de login: {}'.format(response.url))
        token = response.css('input[name="csrf_token"]::attr(value)').extract_first()
        yield scrapy.FormRequest(
            url='http://quotes.toscrape.com/login',
            formdata={
                'username': 'john.doe',
                'password': 'anything',
                'csrf_token': token,
            },
            callback=self.parse_author_links,
        )

    def parse_author_links(self, response):
        has_logout_link = response.css('a[href="/logout"]').extract_first()
        if not has_logout_link:
            raise CloseSpider('falha de autenticação')
        self.log('acabei de fazer login')

        links = response.css('.quote a[href*="goodreads.com"]::attr(href)').extract()
        for link in links:
            yield {'link': link}

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page:
            yield scrapy.Request(
                url=response.urljoin(next_page),
                callback=self.parse_author_links,
            )

Na linha 11 (método parse()), extraímos o token da página para enviar junto com a requisição de autenticação e, logo em seguida, o método parse() gera a requisição POST para a URL de login. Repare que registramos o método self.parse_author_links como o callback para manipular a página enviada como resposta pelo servidor.

No método parse_author_links, verificamos se o botão de logout apareceu na página (o que indica que estamos autenticados) e, caso não esteja, encerramos a execução do spider (linha 25).

Depois disso, basta extrair as URLs dos autores (linhas 28-30). Veja a versão completa do spider aqui.

E o cookie de sessão?

Ah, lembra que falei no começo do artigo que o navegador armazena um cookie de sessão e envia ele junto com as requisições subsequentes pro mesmo site? Pois é, o Scrapy faz a mesma coisa pra gente. Ou seja, não precisamos nos preocupar em manter a sessão. 🙂

Simplificando um pouco

É bem comum termos que submeter valores presentes em campos ocultos (hidden) juntamente com os dados que preenchemos no formulário. Para facilitar a nossa vida, o Scrapy fornece um método chamado FormRequest.from_response(), que carrega automaticamente os valores dos campos do formulário que possuem valores default. Nosso método parse() ficaria assim se utilizássemos o from_response():

def parse(self, response):
    self.log('visitei a página de login: {}'.format(response.url))
    yield scrapy.FormRequest.from_response(
        response,
        formdata={
            'username': 'john.doe',
            'password': 'anything',
        },
        callback=self.parse_author_links,
    )

Como você pode ver, não precisamos extrair e passar o csrf token, nem passar a URL do formulário. O from_response já faz isso pra gente. 🙂

Por fim

Agora você já pode fazer um spider para extrair aqueles dados que você precisa para o seu relatório. Mas, antes de fazer seu spider, verifique se ele não está violando os termos de serviço do seu banco, ou seja lá qual sistema que você estiver acessando. 🙂


por Valdir Stumm Jr em 31 de October de 2017 às 00:01

October 24, 2017

Rodolpho Eckhardt

Agora eu me tornei o destruidor de mundos

Entre os dias 5 e 11 de outubro eu estive em Belo Horizonte para participar da PythonBrasil 13. Entre rever amigos, fazer novos e participar de excelentes discussões, tive a honra de apresentar um keynote aos mais de 570 participantes. Tenho que agradecer muito à organização da PythonBrasil pelo convite e pela oportunidade de apresentar e conversar com uma das minhas comunidades favoritas. A responsabilidade de subir no palco se tornou ainda maior após a publicação da grade de palestras.

24 de October de 2017 às 20:07

October 08, 2017

Thiago Avelino

I would like to leave two complements:

I would like to leave two complements:

awesome-go: https://awesome-go.com

vim bootstrap: https://vim-bootstrap.com

por Avelino em 08 de October de 2017 às 10:32

September 10, 2017

Python Help

Esquisitices (ou não) no arredondamento em Python 3

O arredondamento de números em Python 3 pode ser feito usando uma função builtin chamada round(). Em sua forma mais simples, ela recebe um número qualquer e o arredonda para um número inteiro. Veja ela em ação:

>>> round(1.2)
1
>>> round(1.8)
2

Ela ainda aceita um segundo parâmetro, que indica quantos dígitos de precisão queremos no resultado. O valor padrão desse parâmetro é 0, mas podemos passar qualquer valor:

>>> round(1.847, ndigits=2)
1.85
>>> round(1.847, ndigits=1)
1.8

E o que será que acontece quando queremos arredondar um número como 1.5? A round() arredonda pra cima ou pra baixo? Vamos ver:

>>> round(1.5)
2

Arredonda pra cima? Vamos ver mais um número só pra ter certeza:

>>> round(2.5)
2

Ops, agora foi pra baixo! Vamos ver mais alguns:

>>> round(3.5)
4
>>> round(4.5)
4
>>> round(5.5)
6

Calma aí que tudo tem uma explicação. Em Python 3, a round() define o arredondamento assim:

Arredonda pro mais próximo.
Se empatar, arredonda pro número PAR mais próximo.

Agora faz sentido né? Revendo os exemplos dali de cima, vemos que o arredondamento sempre foi feito pro par mais próximo:

>>> round(3.5)
4
>>> round(4.5)
4
>>> round(5.5)
6

No caso do 3.5, tanto o 3 quanto o 4 estão à mesma distância dele. Ou seja, deu empate. Como a regra determina, round() desempata pro par mais próximo, que é 4. 🙂

E em Python 2?

Em Python 2 é diferente. O empate em arredondamentos é sempre resolvido pra cima, em caso de números positivos:

>>> round(1.5)
2.0
>>> round(2.5)
3.0

E pra baixo, em caso de números negativos:

>>> round(-1.5)
-2.0
>>> round(-2.5)
-3.0

Mas por que Python 3 faz diferente?

O objetivo é deixar o arredondamento menos tendencioso.

Imagine um banco onde todos os arredondamentos são feitos para cima. Ao final do dia, o relatório de receitas do banco vai mostrar um valor mais alto do que o banco realmente recebeu. É exatamente o que acontece em Python 2:

>>> valores = [1.5, 2.5, 3.5, 4.5]
>>> sum(valores)
12.0
>>> sum(round(v) for v in valores)
14.0

Usando a regra de arredondamento de Python 3, os valores arredondados nas operações tendem a ser amortizados, porque metade deles vai ser para cima e a outra metade para baixo, visto que metade dos números existente são pares e a outra metade são ímpares. Veja o mesmo código, agora rodando em Python 3:

>>> valores = [1.5, 2.5, 3.5, 4.5]
>>> sum(valores)
12.0
>>> sum(round(v) for v in valores)
12

Na realidade, isso não é uma novidade de Python 3. Esse tipo de arredondamento é antigo e tem até nome: Bankers Rounding (Arredondamento de Banqueiros).

Se ficou interessado no assunto, dê uma olhada num outro post daqui do blog, que mostra como funciona a divisão inteira em Python: https://pythonhelp.wordpress.com/2013/06/30/comportamento-inesperado-na-divisao-inteira/


por Valdir Stumm Jr em 10 de September de 2017 às 13:10

September 04, 2017

Thiago Avelino

August 27, 2017

Humberto Rocha

Desbravando o pygame 4 - Game of Life

A um tempo atrás eu fiz uma postagem sobre como organizar um coding dojo publicada logo após um dojo de aniversário da comunidade python aqui do DF no qual eu preparei o desafio de implementar o jogo da vida (game of life) do matemático John Horton Conway.

Para deixar este desafio mais interativo preparei um simulador visual com pygame para testar nossa implementação. Este simulador utiliza de todos os conceitos que vimos até agora nesta série e aproveitando o interesse que ressurgiu no Grupy-DF sobre o assunto decidi reforçar os tópicos até então apresentados com a demonstração desta implementação.

O Jogo

O jogo da vida é um jogo sem jogadores, que representa a a dinâmica de interação entre um grupo de seres vivos em um plano bidimensional, partindo de uma configuração inicial e seguindo um conjunto de quatro regras simples.

Este plano possuí um formato tabular dividido em células, sendo que cada célula deste plano pode assumir dois estados, vivo ou morto. Além disso este plano tem dimensões infinitas, porém, limitaremos nosso plano a um recorte finito para simplificar nossa implementação.

A configuração inicial é chamada de semente (seed) e pode ser pré-definida ou aleatória sendo ela alterada a cada ciclo (geração) pelas regras de interação que são as seguintes:

  1. Toda célula com menos de dois vizinhos vivos morre por baixa população;

    regra um

  2. Toda célula com dois ou três vizinhos vivos sobrevive para a próxima geração;

    regra dois

  3. Toda célula com mais de três vizinhos vivos morre por superpopulação;

    regra tres

  4. Toda célula morta com exatamente três vizinhos vivos se torna uma célula viva por reprodução.

    regra quatro

Não há interação entre gerações, ou seja, se por exemplo uma célula nasce, ao se calcular a célula vizinha ela não é levada em conta pois ela não existia na geração vigente.

Implementação

O simulador foi feito para testar a lógica do jogo e se encontra no arquivo simulator.py enquanto no arquivo game.py fica o código responsável pela implementação do game of life. Sendo assim, analisaremos primeiramente o arquivo game.py, que não é o que implementamos no dia do dojo pois não concluímos toda a implementação a tempo, mas é uma versão que eu tinha criado previamente para testar o simulador e demonstrar ao final do dojo caso não chegássemos a implementação completa.

O desafio foi o seguinte, criar uma função game_of_life que recebesse uma seed em forma de uma matriz 50x50 e devolvesse a geração seguinte desta matriz como resultado:

# -*- coding: utf-8 -*-

def cell_check(section):
    '''
    Executa as regras do game of life em um recorte 3x3 para
    saber o estado da célula central
    '''
    # contador de vizinhos
    neighbours = 0
    # referência para o centro do recorte
    center = section[1][1]

    # somando todos os elementos do grupo
    for row in section:
        for cell in row:
            neighbours += cell

    # removendo o valor da célula central para que sobre somente
    # a soma dos vizinhos
    neighbours -= center

    # aplicando as regras do game of life
    # note que a regra dois não precisa ser ativamente aplicada, pois
    # ela não altera o estado da célula avaliada
    if neighbours <= 1:
        # menos de dois vizinhos a célula central morre por baixa população
        center = 0
    elif neighbours == 3:
        # exatamente três a célula nasce por reprodução
        center = 1
    elif neighbours >= 4:
        # mais que três a célula morre de super população
        center = 0

    # retorna o valor da célula central
    return center


def get_section(matrix, row, col):
    '''
    Extrai um recorte 3x3 em um plano tratando as extremidades do plano
    como células sempre mortas
    '''
    # monta um plano 3x3 somente com células mortas para fazer uma cópia
    # da área a ser analizada
    section = [[0 for _ in range(3)] for _ in range(3)]

    # percorre as redondezas da célula de posição row x col copiando seu
    # valor para section exceto quando ultrapassa a borda
    for sec_r, r in enumerate(range(row-1, row+2)):
        for sec_c, c in enumerate(range(col-1, col+2)):
            if r >= 0 and c >= 0 and r < 50 and c < 50:
                section[sec_r][sec_c] = matrix[r][c]

    # devolve o recorte 3x3 do plano
    return section


def game_of_life(seed):
    '''
    Recebe uma seed de um plano 50x50 executa o game of life e devolve
    a geração seguinte
    '''
    # cria um plano vazio para armazenar a nova geração pois não podemos
    # operar diretamente na geração corrente para não gerar efeito colateral
    next_gen = [[0 for _ in range(50)] for _ in range(50)]

    # percorre o plano tirando recortes 3x3 da vizinhança da célula central
    # e os avaliando para descobrir a geração seguinte de cada célula
    for r, row in enumerate(seed):
        for c, col in enumerate(row):
            next_gen[r][c] = cell_check(get_section(seed, r, c))

    # devolve a geração seguinte
    return next_gen

Como visto na implementação o código de game.py executa somente uma geração do game of life, o que é excelente para o nosso simulador.py, pois ele utilizará do nosso conceito de loop de jogo e a cada ciclo atualiza o jogo com a geração seguinte e desenha seu estado na tela:

# -*- coding: utf-8 -*-

import random
import time

import pygame

from game import game_of_life

# plano vazio
SEED = [[0 for _ in range(50)] for _ in range(50)]

# Glider
SEED[23][24] = 1
SEED[24][25] = 1
SEED[25][23] = SEED[25][24] = SEED[25][25] = 1

# Glider gun
# SEED[20][30] = 1
# SEED[21][28] = SEED[21][30] = 1
# SEED[22][18] = SEED[22][19] = SEED[22][26] = SEED[22][27] = SEED[22][40] = SEED[22][41] = 1
# SEED[23][17] = SEED[23][21] = SEED[23][26] = SEED[23][27] = SEED[23][40] = SEED[23][41] = 1
# SEED[24][6]  = SEED[24][7]  = SEED[24][16] = SEED[24][22] = SEED[24][26] = SEED[24][27] = 1
# SEED[25][6]  = SEED[25][7]  = SEED[25][16] = SEED[25][20] = SEED[25][22] = SEED[25][23] = SEED[25][28] = SEED[25][30] = 1
# SEED[26][16] = SEED[26][22] = SEED[26][30] = 1
# SEED[27][17] = SEED[27][21] = 1
# SEED[28][18] = SEED[28][19] = 1

# Rich's p16
# SEED[19][20] = SEED[19][21] = SEED[19][22] = SEED[19][26] = SEED[19][27] = SEED[19][28] = 1
# SEED[20][19] = SEED[20][23] = SEED[20][25] = SEED[20][29] = 1
# SEED[21][19] = SEED[21][23] = SEED[21][25] = SEED[21][29] = 1
# SEED[22][18] = SEED[22][20] = SEED[22][21] = SEED[22][22] = SEED[22][23] = SEED[22][25] = SEED[22][26] = SEED[22][27] = SEED[22][28] = SEED[22][30] = 1
# SEED[23][18] = SEED[23][19] = SEED[23][29] = SEED[23][30] = 1
# SEED[26][22] = SEED[26][23] = SEED[26][22] = SEED[26][25] = SEED[26][26] = 1
# SEED[27][21] = SEED[27][23] = SEED[27][25] = SEED[27][27] = 1
# SEED[28][22] = SEED[28][26] = 1

# Random
# SEED = [[random.choice([0, 1]) for _ in range(50)] for _ in range(50)]

pygame.init()

screen = pygame.display.set_mode((550, 550))


def draw_matrix(matrix):
    '''
    Função para desenhar o plano
    '''

    # preenche a tela de preto
    screen.fill([0, 0, 0])

    # percorre o plano desenhando as células com seus valores
    for r, row in enumerate(matrix):
        for c, cell in enumerate(row):
            if cell:
                # caso a célula esteja viva, a pinte de branco
                pygame.draw.rect(screen, (255, 255, 255),
                                 (11*c, 11*r, 10, 10))


# define a seed como um dos valores de exemplo do começo
seed = SEED

# desenha o estado inicial de nossa seed
draw_matrix(seed)

pygame.display.flip()

# espera por 1 segundo para podermos contemplar o estado de partida
time.sleep(1)

while True:
    # PROCESSAMENTO DE ENTRADA
    event = pygame.event.poll()

    if event.type == pygame.QUIT:
        # finaliza o programa caso clique em fechar
        break
    elif (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
        # finaliza o programa caso pressione a tecla ESC
        break

    # ATUALIZAÇÃO DO JOGO

    # aplica o game of life para processar a geração seguinte
    seed = game_of_life(seed)

    # DESENHO

    # desenha a nova geração na tela
    draw_matrix(seed)

    pygame.display.flip()

    # espera um breve momento antes de partir para o próximo ciclo
    time.sleep(0.05)

Desenhamos uma vez a seed na tela antes de iniciar o loop para dar tempo do usuário ver o estado inicial na tela. Em seguida damos início ao loop de jogo checando por eventos como feito anteriormente mas com a adição da condição de parada utilizando a tecla ESC para finalizar o programa. Na etapa de atualização do jogo rodamos a função game_of_life do arquivo game.py para processar a geração seguinte e finalizamos com a etapa de desenho, agora com um tempo de espera menor entre os ciclos para ter uma visão mais dinâmica da evolução da nossa seed.

Exemplos

Para demonstrar o simulador em funcionamento utilizei o glider que é o padrão mais famoso dentro do game of life e foi absorvido pela comunidade hacker como seu emblema. Ele é o menor padrão cíclico que realiza deslocamento pelo, plano:

glider

Obs.: Note que existem alguns padrões comentados no código do simulador, você pode remover o comentário de qualquer um deles para ver padrões diferentes em ação.

Conclusão

O game of life é um conceito muito bom para se aprender conceitos da computação, recomendo assistir as entrevistas com seu criador pelo canal Numberphile no youtube.

E como sempre o código está disponível no repositório para todos.

por Humberto Rocha em 27 de August de 2017 às 00:00

August 24, 2017

Bruno Cezar Rocha

Simple Login Extension for Flask

Travis PyPI PyPI PyPI Flask

Login Extension for Flask

There are good and recommended options to deal with web authentication in Flask.

I recommend you use:

Those extensions are really complete and production ready!

So why Flask Simple Login?

However sometimes you need something simple for that small project or for prototyping.

Flask Simple Login

What it provides:

  • Login and Logout forms and pages
  • Function to check if user is logged-in
  • Decorator for views
  • Easy and customizable login_checker

What it does not provide: (but of course you can easily implement by your own)

  • Database Integration
  • Password management
  • API authentication
  • Role or user based access control

Hot it works

First install it from PyPI.

pip install flask_simplelogin

from flask import Flask
from flask_simplelogin import SimpleLogin

app = Flask(__name__)
SimpleLogin(app)

That's it! now you have /login and /logout routes in your application.

The username defaults to admin and the password defaults to secret (yeah that's not clever, let's see how to change it)

Configuring

Simple way

from flask import Flask
from flask_simplelogin import SimpleLogin

app = Flask(__name__)
app.config['SECRET_KEY'] = 'something-secret'
app.config['SIMPLELOGIN_USERNAME'] = 'chuck'
app.config['SIMPLELOGIN_PASSWORD'] = 'norris'

SimpleLogin(app)

That works, but is not so clever, lets use env vars.

$ export SIMPLELOGIN_USERNAME=chuck
$ export SIMPLELOGIN_PASSWORD=norris

then SimpleLogin will read those env vars automatically.

from flask import Flask
from flask_simplelogin import SimpleLogin

app = Flask(__name__)
app.config['SECRET_KEY'] = 'something-secret'
SimpleLogin(app)

But what if you have more users and more complex auth logic? write a custom login checker

Using a custom login checker

from flask import Flask
from flask_simplelogin import SimpleLogin

app = Flask(__name__)
app.config['SECRET_KEY'] = 'something-secret'


def only_chuck_norris_can_login(user):
    "user = {'username': 'foo', 'password': 'bar'}"
    # do the authentication here, it is up to you!
    # query your database, check your user/passwd file
    # connect to external service.. anything.
    if user.get('username') == 'chuck' and user.get('password') == 'norris':
       return True  # Allowed
    return False  # Denied


SimpleLogin(app, login_checker=only_chuck_norris_can_login)

Checking if user is logged in


from flask_simplelogin import is_logged_in

if is_logged_in():
    # do things if anyone is logged in

if is_logged_in('admin'):
    # do things only if admin is logged in

Decorating your views

from flask_simplelogin import login_required

@app.route('/it_is_protected')
@login_required   # < --- simple decorator
def foo():
    return 'secret'

Protecting Flask Admin views


from flask_admin.contrib.foo import ModelView
from flask_simplelogin import is_logged_in


class AdminView(ModelView)
    def is_accessible(self):
        return is_logged_in('admin')

Customizing templates

There are only one template to customize and it is called login.html

Example is:

{% extends 'base.html' %}
{% block title %}Login{% endblock %}
{% block messages %}
   {{super()}}
   {%if form.errors %}
     <ul class="alert alert-danger">
       {% for field, errors in form.errors.items() %}
         <li>{{field}} {% for error in errors %}{{ error }}{% endfor %}</li>
       {% endfor %}
     </ul>
   {% endif %}
{% endblock %}

{% block page_body %}
       <form action="{{ url_for('simplelogin.login', next=request.args.get('next', '/')) }}" method="post">
            <div class="form-group">
            {{ form.csrf_token }}
            {{form.username.label}}<div class="form-control">{{ form.username }}</div><br>
            {{form.password.label}}<div class="form-control"> {{ form.password }}</div><br>
            </form>
           <input type="submit" value="Send">
       </form>
{% endblock %}

Take a look at the example app.

And you can customize it in anyway you want and need, it receives a form in context and it is a WTF form the submit should be done to request.path which is the same /login view.

You can also use {% if is_logged_in %} in your template if needed.

Requirements

  • Flask-WTF and WTForms
  • having a SECRET_KEY set in your app.config

por Bruno Rocha em 24 de August de 2017 às 11:03

Humberto Rocha

Desbravando o pygame 3 - Game Loop

Agora que sabemos como desenhar na tela (postagem anterior) seria interessante que nosso jogo ficasse rodando até que alguém o feche. Para isso vamos utilizar um dos fundamentos do desenvolvimento de jogos que é o Game Loop.

Conceito

Dentro da computação o conceito de ciclo é bastante recorrente, trata-se de uma sequência de ações e tomadas de decisão programadas que se repetem dentro de um laço de repetição (ou loop). No baixo nível temos o processador do computador que trabalha em ciclos de operações aritméticas gerenciadas pelo clock, em sistemas operacionais o ciclo é quem gerencia o tempo de uso do processador, o dividindo entre todas as aplicações do sistema além de verificar se o usuário realizou algum comando, em servidores web usamos um ciclo para verificar se alguém requisitou uma página que será processada e devolvida em forma de resposta. Com jogos não seria diferente.

O ciclo dentro do desenvolvimento de jogos é chamado de Game Loop e é peça fundamental na estrutura de um jogo. De modo simplificado temos a seguinte estrutura:

loop basico

Durante a execução jogo está sempre:

  • Recebendo e processando entradas do usuário que são detectados por eventos gerados por dispositivos de entrada como um teclado e um mouse.

  • Atualizando o jogo de acordo com os eventos processados e de outras mecânicas como aplicação de gravidade, detecção de colisão, eventos programados dentre outros.

  • Desenhando na tela o resultado de toda a interação anterior.

Diferente de um loop de eventos de uma aplicação de desktop por exemplo em que o programa fica parado até que você tome alguma ação, no loop de jogo continua sua execução independente da entrada do usuário atualizando elementos independentes como, música, física, inteligência artificial, etc.

Um outro ponto importante que não está nos itens acima mas está presente na grande maioria dos jogos é o controle de atualização do loop. Mas este tópico ficará para uma próxima postagem.

Implementação

Para demonstrar o game loop em funcionamento faremos um programa que desenha uma bola que quica pelos cantos da tela:

# -*- coding: utf-8 -*-

import pygame

# definindo cores
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

pygame.init()

screen = pygame.display.set_mode((640, 480))

pygame.display.set_caption('Game Loop')

# variáveis da bola
position_x = 300
position_y = 200
velocity_x = 1
velocity_y = 1

# iniciando o loop de jogo
while True:
    # PROCESSAMENTO DE ENTRADA

    # capturando eventos
    event = pygame.event.poll()
    # caso o evento QUIT (clicar no x da janela) seja disparado
    if event.type == pygame.QUIT:
        # saia do loop finalizando o programa
        break

    # ATUALIZAÇÃO DO JOGO

    # movendo a bola
    position_x += velocity_x
    position_y += velocity_y

    # mudando a direção no eixo x nas bordas
    if position_x > 600:
        velocity_x = -1
    elif position_x < 0:
        velocity_x = 1

    # mudando a direção no eixo y nas bordas
    if position_y > 440:
        velocity_y = -1
    elif position_y < 0:
        velocity_y = 1

    # DESENHO

    # preenchendo o fundo com preto
    screen.fill(BLACK)

    # desenhando a bola
    pygame.draw.ellipse(screen, RED, [position_x, position_y, 40, 40])

    # atualizando a tela
    pygame.display.flip()

É assim, bem direto ao ponto, o loop definido em while True: irá rodar o código indefinidamente realizando as etapas de processamento de entrada, atualização do jogo e desenho a cada ciclo que passa.

Na etapa de processamento de entrada usamos pygame.event.poll() para recuperar um evento da fila e checar se ele é um evento de término de programa. Se ele for, usamos break para sair do loop, finalizando assim o programa.

Durante a etapa de atualização do jogo movemos a nossa bola no eixo x e y verificando se a bola se encontra nas extremidades e alterando sua direção em caso positivo.

E finalmente na etapa de desenho usamos as funções previamente apresentadas para mostrar nossa bola na tela.

Note que sempre pintamos o fundo de preto, caso contrário o rastro da bola ficaria na tela.

Concluindo

E com isso finalizamos nosso breve passeio pelo game loop. Não se esqueça que todo código estará disponível no repositório desbravando-pygame.

por Humberto Rocha em 24 de August de 2017 às 00:00

August 23, 2017

Bruno Cezar Rocha

Publish your Python packages easily using flit

Deploying Python Packages to PyPI using Flit

The traditional way of package deployment in Python is using a setup.py script located in the root of your project and then running python setup.py sdist upload to release a new version.

It works by using distutils, setuptools or distribute and there is also twine which is a command line application to manage uploads.

History

distutils is the standard way of Python package distribution included in standard library since Python 2.x then setuptools was created to overcome distutils limitations and also introduces a command line application called easy_install (currently we use its sucessor called pip) and also setuptools introduced a very handy feature called pkg_resources. One of the characteristics of setuptools is that it uses Monkey Patching over the standard distutils to fix existing problems.

Other forks of setuptools has been created to fix that issues and add common developers preferences so well known forks like distribute and distutils2 and distlib has been merged back to the original setuptools

Lots of other packaging tools has been created to try to fix the distribution problems, some maintained by PyPA (Python Package Authority) and some maintained by community.

How it works in standard way

using one of the above you should create a file called setup.py in the root of your project, e.g:

from <my_favorite_dist_tool> import setup

# Example taken from Django's repository
setup(
    name='Django',
    version=version,
    url='https://www.djangoproject.com/',
    author='Django Software Foundation',
    author_email='foundation@djangoproject.com',
    description=('A high-level Python Web framework that encourages '
                 'rapid development and clean, pragmatic design.'),
    license='BSD',
    packages=find_packages(exclude=EXCLUDE_FROM_PACKAGES),
    include_package_data=True,
    scripts=['django/bin/django-admin.py'],
    entry_points={'console_scripts': [
        'django-admin = django.core.management:execute_from_command_line',
    ]},
    install_requires=['pytz'],
    extras_require={
        "bcrypt": ["bcrypt"],
        "argon2": ["argon2-cffi >= 16.1.0"],
    },
    zip_safe=False,
    classifiers=[
        'Development Status :: 2 - Pre-Alpha',
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Topic :: Internet :: WWW/HTTP',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
        'Topic :: Internet :: WWW/HTTP :: WSGI',
        'Topic :: Software Development :: Libraries :: Application Frameworks',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ],
)

As you can see it is very confusing to decide which of the distribute tools to addopt and how the setup.py should be writen because there are different examples over the github most famous Python repositories.

Making things easier with Flit

NOTE: Forget all about all that history and setup.py you read above and consider using only Flit.

Flit is a simple way to Package and deploy Python projects on PyPI, Flit makes it easier by using a simple flit.ini file and assumes common defaults to save your time and typing.

I knew about Flit when I was taking a look at Mariatta Wijaya game called Tic Tac Taco Pizza and noticed that she used flit to deploy the game, so we also asked her the reason for using this on the podcast we recorded so I decided to try porting my projects to Flit.

How it works?

Instead of a complex setup.py you put a simple flit.ini in the root of your project and it looks like:

[metadata]
module = markdocs
author = Bruno Rocha
author-email = rochacbruno@gmail.com
maintainer = rochacbruno
maintainer-email = rochacbruno@gmail.com
home-page = https://github.com/rochacbruno/markdocs
requires = mistune
           click
description-file = README.md
classifiers = Programming Language :: Python :: 3.6
              Intended Audience :: Developers
              License :: OSI Approved :: MIT License
              Topic :: Documentation
              Topic :: Software Development :: Documentation
              Topic :: Software Development :: Quality Assurance
requires-python = >=3.6

[scripts]
markdocs = markdocs:main

Now you only need to have flit installed in your local machine pip3 install flit (unsing pip3 as flit works only in Python3+) and optionally is recommended to have pandoc and pypandoc installed because Flit can convert your README.md into the .rst, the format still used by PyPI (note that Markdown support is coming to PyPi.org soon).

The advantages are:

  • No more complicated setup.py
  • If you omit some fields it will assume common used defaults
  • It is easier to read and write
  • Will convert your README.md
  • Will take the __version__ included in your program/__init__.py
  • Can deploy to TestPyPI
  • Avoids over engineering on setup.py

Development installation

To install your package during development use

flit install --symlink --python path/to/virtualenv/bin/python

the --python is optional, by default will take the current which python

Registering and deploying

It is easy and will register the new project for you if doesn't exist on PyPI

flit publish

Flit packages a single importable module or package at a time, using the import name as the name on PyPI. All subpackages and data files within a package are included automatically.

Important!

  • Flit will use the data from ~/.pypirc to authenticate and to find the server deployment addresses
  • You can also set FLIT_USERNAME and FLIT_PASSWORD and FLIT_INDEX_URL as environment variables which makes flit good for CI deployment (e.g: TravisCI)

What is missing?

NOTE: Flit is open-source, so some of this things are already under consideration and there are PRs opened.

  • Flit will not bump your project version automatically, you can still use tools like bumpversion but this feature would be better if builtin
  • Flit will not parse a requirements.txt file and would be nice to have it as tools like pyup.io can track those files but not flit.ini yet
  • Flit does not create a .lock frozen file with the version used on specific release and it is interesting just like pipenv does

Conclusion

Flit is the easier way to deploy packaged to PyPI following 3 simple steps.

  1. Install flit
  2. Describe your flit.ini
  3. Run flit publish

then your library is released to PyPI.

However

Python still needs better standards because you still need separated tools to make common tasks and using a single tool to that tasks (pack, install, deploy, create) would be better (just like what cargo does for Rust), instead in Python you have:

  • Flit to deploy packages to PyPI for distribution
  • bumpversion to bump your semver number
  • pip to install and update packages from PyPI (or pipenv/WIP to do the same with more powers)
  • Cookiecutter to create a new Python Package (from strucured templates)
  • safety to check dependencies security
  • flake or pylint to static and styling checks
  • venv, pyenv or virtualenvwrapper to manage isolated environments
  • pytest to run tests
  • pyup.io to watch for depdendency updates
  • tox for testing on multiple environments
  • sphinx to create documentation
    • lots of other options

Having so many tools brings a lot of confusion and makes it hard to choose, why not having a single tool, based on different plugins sharing the same API?

Just an idea

python -m manage [install packagename]               # <-- calls pip or pipenv
                 [publish --options --bump=version]  # <-- calls `flit` and `bumpversion`
                 [new packagename templatename]      # <-- calls cookiecutter
                 [safecheck]                         # <-- calls safety
                 [checkupdates]                      # <-- checks in the same way as Pyup.io does
                 [test path]                         # <-- calls pytest, nose or unittest
                 [lint path]                         # <-- calls flake, pylint 
                 [venv options]                      # <-- calls the existing venv module
                 [docs --options]                    # <-- calls sphinx, pydoc, markdocs or other

All above configurable via config file or env vars and each of that endpoints would be provided by many plugins sharing the same API, so you could choose between flit or twine as your publish manager etc..

So maybe I can implement that features in manage

Please share in comments if you know some other Python management tool

por Bruno Rocha em 23 de August de 2017 às 11:23

August 20, 2017

Thiago Avelino

August 13, 2017

Thiago Avelino

Great blogpost, congratulations on the superaction…

Great blogpost, congratulations on the superaction…

Whenever you need to talk (about any subject) I’m here.

por Avelino em 13 de August de 2017 às 20:53

July 31, 2017

Flavio Ribeiro

Using Open Source to Create a Video Thumbnail Service

Using Open Source to Create a Video Thumbnail Service

Last week The New York Times hosted the 2017’s edition of Makers Week, an entire week dedicated to working on projects and ideas employees want to test, build and innovate on.

There are no boundaries to projects, nor specific scope requirements. You can use your time to do research on new topics or disciplines, contribute to open source projects, fix bugs or create products from scratch. It's definitely not a new thing and I believe most companies are doing this now so I will not dwell on it. If you want to know more about how this works here at our office, you can read this article from last year’s edition.

Increasing our click through rate

One of our goals as a video team this year is to increase our click through rate when our users see our video player. We usually do a pretty good job of selecting thumbnails for all of our videos — often using photos taken by our own photo journalists. However, after watching a talk from JWPlayer folks and reading a research article from Netflix, it became clear to me that we can't just assume a thumbnail is good for a given video. We should actually use some data and run A/B tests to come up with the best one.

In order to try to improve our thumbs, we'd need to be able to create them in a cleverer and faster way. So I thought that creating something to generate and serve thumbnails for any of our videos at any time, on-the-fly (on the time of an HTTP request), would be a great project for my Makers Week. When working at Globo.com, I saw some amazing open source projects being created and maintained by some wonderful engineers I met over there so the scope of the project would be nothing more than putting some of those projects together.

Lumberjack

Before starting, I invited Francisco Souza to help me. He's a specialist when it comes to application deployment and all things related to Docker/Kubernetes/Google Cloud Service. I'm glad he accepted, things were much easier with his help.

Using Open Source to Create a Video Thumbnail Service

As I said before, Lumberjack is a combination of open source projects that allow thumbnails extraction for videos. It leverages the powers of Thumbor, NGINX, nginx-thumbextractor module (or simply módulo do Wanden as we like to say in Portuguese) and the Lua programming language in order to extract and generate pictures for any given moment of any given video of our video library.

Architecture

We are deploying three services in different containers in a Kubernetes pod:

  • NGINX + Video Thumbnails Extractor Module: Responsible for scanning the video on a given mountpoint, extracting the frame of a given timecode and returning it on the fly. We used gcsfuse to mount our production GCS bucket where our videos library reside, allowing the module to go there and get the frame requested.

  • Thumbor: Responsible for applying filters, crops and resizing of images. Thumbor relies on different engines including OpenCV to apply functions passed as query strings on the HTTP request. It includes smart cropping, face and assets detections and a bunch of other cool stuff. You should take a look on this powerful project and see what it can do for you. The Docker image we are using for this service is available here.

  • NGINX + Lua Application: A simple Lua script that runs inside NGINX with this Lua Module. Given a VideoID, the script is able to fetch the right MP4 video asset from our internal API and send to the thumbnails extractor. The application is also responsible for getting the parameters passed from the user on the URL such as filters and resolution, send to Thumbor and return the final picture back to the user.

Using Open Source to Create a Video Thumbnail Service

We decided not to open source Lumberjack. At the end of the day the whole project is just some business logics around the open source projects I mentioned on this post. So really all of the credit belongs to the guys who created and maintain Thumbor and the thumbextractor module. If you want something similar to what we did you can just deploy them.

Some Use Cases

Imagine we want to crop a frame from this video at 30 seconds to use as the cover of another vertical video on mobile phones (9x16), in grayscale, with smart cropping. We just need to pass the below as parameters and the service will make it for you:

lumberjack.nyt.net/video-id?timecode=30&filters=grayscale()&resolution=900x1600&smartcrop=true

Using Open Source to Create a Video Thumbnail Service

Another great feature supported by the thumbnails extractor is the generation of sprites or tiles. We can, for example, generate a sprite map with thumbnails every one second and use it as a moving cover when the user hovers on it:

lumberjack.nyt.net/video-id?sprite=true&size=100&interval=1

Using Open Source to Create a Video Thumbnail Service

See how it looks by placing the pointer over the image below:

We can also use the similar approach to use the sprite map for the thumbnails on the seekbar as you can see on this player:

Future

The Thumbnails Service is now part of our Q4 roadmap and Lumberjack is already in production. We didn't present to the newsroom yet as we are still facing some performance issues with caching and the GCS bucket. The NGINX locations are not optimized so we'll need to look back on them as well. To sum up, we'll need to revisit a lot of stuff that we did in a rush during Makers week (I don't even need to mention that we have zero tests for the Lua script too).

For the future, we want to be able to detect perfect looping GIF's for using them on social channels and also detect highlights of a video based on the audio and closed captions. That way we can suggest thumbnails for our newsroom editors within our CMS. Finally, we'd love to integrate thumbnails tests with our A/B framework the same way Netflix did.

por Flávio Ribeiro em 31 de July de 2017 às 21:20

July 27, 2017

Thiago Avelino

Parabéns Lucas, você é fera cara.

Parabéns Lucas, você é fera cara. É extremamente importe mudar e encerrar ciclos, nosso vida é feita por ciclos.

Não sei se chegou ver esse blogpost que escrevi sobre encerrar ciclos, acho legal ler: https://medium.com/@avelino0/encerrando-ciclos-viva-no-presente-n%C3%A3o-se-paralise-pelo-passado-a6ec11a62993

por Avelino em 27 de July de 2017 às 05:40

July 23, 2017

Blog do PauloHRPinheiro

July 21, 2017

PythonClub

Peewee - Um ORM Python minimalista

Peewee é um ORM destinado a criar e gerenciar tabelas de banco de dados relacionais através de objetos Python. Segundo a wikipedia, um ORM é:

Mapeamento objeto-relacional (ou ORM, do inglês: Object-relational mapping) é uma técnica de desenvolvimento utilizada para reduzir a impedância da programação orientada aos objetos utilizando bancos de dados relacionais. As tabelas do banco de dados são representadas através de classes e os registros de cada tabela são representados como instâncias das classes correspondentes.

O que o ORM faz é, basicamente, transformar classes Python em tabelas no banco de dados, além de permitir construir querys usando diretamente objetos Python ao invés de SQL.

O Peewee é destinado a projetos de pequeno/médio porte, se destacando pela simplicidade quando comparado a outros ORM mais conhecidos, como o SQLAlchemy. Uma analogia utilizada pelo autor da API e que acho muito interessante é que Peewee está para o SQLAlchemy assim como SQLite está para o PostgreSQL.

Em relação aos recursos por ele oferecidos, podemos citar que ele possui suporte nativo a SQLite, PostgreSQL e MySQL, embora seja necessário a instalação de drivers para utilizá-lo com PostgreSQL e MySQL e suporta tanto Python 2.6+ quanto Python 3.4+.

Neste tutorial, utilizaremos o SQLite, por sua simplicidade de uso e por não precisar de nenhuma configuração.

Instalação

O Peewee pode ser facilmente instalado com o gerenciador de pacotes pip:

pip install peewee

Criando o banco de dados

Para criar as tabelas é bem simples. Inicialmente passamos o nome do nosso banco de dados (a extensão *.db indica um arquivo do SQLite).

import peewee

db = peewee.SqliteDatabase('codigo_avulso.db')

Diferente de outros bancos de dados que funcionam através um servidor, o SQLite cria um arquivo de extensão *.db, onde todos os nossos dados são armazenados.

DICA: caso deseje ver as tabelas existentes no arquivo codigo_avulso.db, instale o aplicativo SQLiteBrowser. Com ele fica fácil monitorar as tabelas criadas e acompanhar o tutorial.

 sudo apt-get install sqlitebrowser

A título de exemplo, vamos criar um banco destinado a armazenar nomes de livros e de seus respectivos autores. Comecemos primeiro com a classe que representa os autores.

import peewee

db = peewee.SqliteDatabase('codigo_avulso.db')

class Author(peewee.Model):
    """
    Classe que representa a tabela Author
    """

    # A tabela possui apenas o campo 'name', que
    # receberá o nome do autor
    name = peewee.CharField()

    class Meta:
        # Indica em qual banco de dados a tabela
        # 'author' sera criada (obrigatorio). Neste caso,
        # utilizamos o banco 'codigo_avulso.db' criado anteriormente.
        database = db

Em seguida, criamos a classe que representa os livros. Ela possui uma relação de "muitos para um" com a tabela de autores, ou seja, cada livro possui apenas um autor, mas um autor pode possuir vários livros.

import peewee

db = peewee.SqliteDatabase('codigo_avulso.db')

class Book(peewee.Model):
    """
    Classe que representa a tabela Book
    """

    # A tabela possui apenas o campo 'title', que
    # receberá o nome do livro
    title = peewee.CharField()

    # Chave estrangeira para a tabela Author
    author = peewee.ForeignKeyField(Author)

    class Meta:
        # Indica em qual banco de dados a tabela
        # 'author' sera criada (obrigatorio). Neste caso,
        # utilizamos o banco 'codigo_avulso.db' criado anteriormente.
        database = db

Agora, vamos reunir tudo em um único arquivo model.py. Como exemplo, eu criei um arquivo main.py para utilizarmos as classes que acabamos de criar.

import peewee
from model import Author, Book


if __name__ == '__main__':
    try:
        Author.create_table()
    except peewee.OperationalError:
        print 'Tabela Author ja existe!'

    try:
        Book.create_table()
    except peewee.OperationalError:
        print 'Tabela Book ja existe!'

Após executarmos o código, será criado um arquivo de nome codigo_avulso.db no mesmo diretório do nosso arquivo main.py, contendo as tabelas Author e Book. A estrutura do diretório ficou assim:

.
├── codigo_avulso.db
├── main.py
├── model.py

Inserindo dados no banco

Agora, vamos popular nosso banco com alguns autores e seus respectivos livros. Isso pode ser feito de dois modos. Através do método create, quando desejamos inserir um registro apenas; ou pelo método insert_many, quando desejamos inserir vários registros de uma vez em uma mesma tabela.

# Inserimos um autor de nome "H. G. Wells" na tabela 'Author'
author_1 = Author.create(name='H. G. Wells')

book_1 = {
    'title': 'A Máquina do Tempo',
    'author': author_1,
}

book_2 = {
    'title': 'Guerra dos Mundos',
    'author': author_1,
}

# Inserimos um autor de nome "Julio Verne" na tabela 'Author'
author_2 = Author.create(name='Julio Verne')

book_3 = {
    'title': 'Volta ao Mundo em 80 Dias',
    'author': author_2,
}

book_4 = {
    'title': 'Vinte Mil Leguas Submarinas',
    'author_id': author_1,
}

books = [book_1, book_2, book_3, book_4]

# Inserimos os quatro livros na tabela 'Book'
Book.insert_many(books).execute()

Consultando dados no banco

O Peewee possui comandos destinados a realizar consultas no banco. De maneira semelhante ao conhecido SELECT. Podemos fazer essa consulta de duas maneiras. Se desejamos o primeiro registro que corresponda a nossa pesquisa, podemos utilizar o método get().

book = Book.get(Book.title == "Volta ao Mundo em 80 Dias").get()
book.title

Porém, se desejamos mais de um registro, utilizamos o método select. Por exemplo, para consultar todos os livros escritos pelo autor "H. G. Wells".

books = Book.select().join(Author).where(Author.name=='H. G. Wells')

# Exibe a quantidade de registros que corresponde a nossa pesquisa
print books.count()

for book in books:
    book.title

# Resultado:
# * A Máquina do Tempo
# * Guerra dos Mundos
# * Vinte Mil Leguas Submarinas

Também podemos utilizar outras comandos do SQL como limit e group (para mais detalhes, ver a documentação aqui).

Alterando dados no banco

Alterar dados também é bem simples. No exemplo anterior, se observarmos o resultado da consulta dos livros do autor "H. G. Wells", iremos nos deparar com o livro de título "Vinte Mil Léguas Submarinas". Se você, caro leitor, gosta de contos de ficção-científica, sabe que esta obra foi escrito por "Julio Verne", coincidentemente um dos autores que também estão cadastrados em nosso banco. Sendo assim, vamos corrigir o autor do respectivo livro.

Primeiro vamos buscar o registro do autor e do livro:

new_author = Author.get(Author.name == 'Julio Verne')
book = Book.get(Book.title=="Vinte Mil Leguas Submarinas")

Agora vamos alterar o autor e gravar essa alteração no banco.

# Alteramos o autor do livro
book.author = new_author

# Salvamos a alteração no banco
book.save()

Deletando dados do banco

Assim como as operações anteriores, também podemos deletar registros do banco de maneira bem prática. Como exemplo, vamos deletar o livro "Guerra dos Mundos" do nosso banco de dados.

# Buscamos o livro que desejamos excluir do banco
book = Book.get(Book.title=="Guerra dos Mundos")

# Excluimos o livro do banco
book.delete_instance()

Simples não?

Conclusão

É isso pessoal. Este tutorial foi uma introdução bem enxuta sobre o Peewee. Ainda existem muitos tópicos que não abordei aqui, como a criação de primary_key, de campos many2many entre outros recursos, pois foge do escopo deste tutorial. Se você gostou do ORM, aconselho a dar uma olhada também na sua documentação, para conseguir extrair todo o potencial da ferramenta. A utilização de um ORM evita que o desenvolvedor perca tempo escrevendo query SQL e foque totalmente no desenolvimento de código.

O Peewee também possui suporte ao flamework flask, então dependendo do tamanho do projeto, pode ser uma alternativa interessante no lugar de ORM mais complexos como o SQLAlchemy.

É isso pessoal. Obrigado pela leitura e até o próximo tutorial!

Referências

por Michell Stuttgart em 21 de July de 2017 às 02:45

July 10, 2017

Rodrigo Delduca

Criando um bot de notícias para o Telegram usando Scrapy e Firebase

Problema

Eu costumo pegar com frequência a rodovia Régis Bittencourt e o que acontece com frequência é o trânsito parar completamente no meio do nada e sem acesso à internet, então eu fico sem a mínima noção do que está acontecendo e em quanto tempo conseguirei chegar ao meu destino.

Pensando nisso, decidi escrever um pequeno bot para o Telegram que publica num canal as notícias da estrada! Como de costume no NULL on error, vou explicar como fiz.

Web scraping

Squitter

O primeiro passo é extrair as informações do site. Eu optei por utilizar o framework Scrapy, por alguns motivos que ficarão bem claros abaixo e por ter bastante experiência escrevendo web crawlers com o Scrapy - eu não pretendo escrever um tutorial a respeito neste artigo, isso ficará para uma próxima oportunidade.

Antes de tudo, eu preciso definir o que eu quero extrair; isso é feito definindo uma classe com N propriedades herdando de scrapy.Item

class Entry(Item):
    uid = Field()
    spider = Field()
    timestamp = Field()
    content = Field()

Como é possível notar, a aranha, ou crawler, ficou bem simples, mas vou explicar cada parte a seguir.

class RegisSpider(CrawlSpider):
    name = 'regis'
    allowed_domains = ['autopistaregis.com.br']
    start_urls = ['http://www.autopistaregis.com.br/?link=noticias.todas']
    rules = (
        Rule(LinkExtractor(allow=r'\?link=noticias.?ver*'), callback='parse_news'),
    )

    def parse_news(self, response):
        loader = EntryLoader(item=Entry(), response=response)
        loader.add_xpath('content', '//*[@id="noticia"]/p[not(position() > last() -3)]//text()')
        return loader.load_item()

A propriedade start_urls indica onde a aranha vai iniciar a varredura de páginas

Após isso, definimos algumas regras. Vou usar um LinkExtractor, que, como o próprio nome diz é um componente para extrair links seguindo uma regra das páginas encontradas. Nesse caso eu usei uma expressão regular que bate com todas as URLS de notícias do site, e defino um callback que será chamado para cada página, chamado parse_news.

LinkExtractor(allow=r'\?link=noticias.?ver*'), callback='parse_news')

Então é aqui que a mágica toda acontece: passei algum tempo analisando o código fonte da página e usando o inspetor web para poder gerar um xpath que bata com notícia, excluindo as informações extras na página.

XPath

O XPath é uma forma de atravessar o HTML e extrair alguma informação específica. É uma linguagem bem poderosa. Nesse caso eu usei a expressão [not(position() > last() -3)] para excluir os últimos 3 parágrafos marcados pela tag <p>, que o site sempre coloca como uma forma de rodapé. Infelizmente, nem sempre os sites seguem boas práticas, o que me facilitaria e muito a extração dos dados!

loader.add_xpath('content', '//*[@id="noticia"]/p[not(position() > last() -3)]//text()')

Os outros campos, como ID da noticía e timestamp são “extraídos” usando um middleware chamado scrapy-magicfields, desta maneira:

MAGIC_FIELDS = {
    'uid': "$response:url,r'id=(\d+)'",
    'spider': '$spider:name',
    'timestamp': "$time",
}

O próximo passo é rodar o web crawler periodicamente. Eu usei o sistema de cloud do Scrapinghub, que é a empresa que desenvolve o Scrapy e outras ferramentas de scraping; nele, eu posso configurar para rodar de tempos em tempos o crawler. No meu caso, eu configurei para rodar a cada 30 minutos,

Mesmo que possível, eu não posso publicar diretamente, apenas as novas notícias, caso contrário, toda vez que o crawler rodar eu estaria poluindo o canal com as notícias repetidas. Então eu decidi salvar num banco de dados intermediário para conseguir distinguir o que é novo do que já foi indexado.

Persistência

Eis que entra o Firebase, e sua nova funcionalidade chamada de functions, com o qual, eu posso escrever uma função que reage a determinados eventos no banco de dados - por exemplo, quando um novo dado é inserido.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request-promise');
const buildUrl = require('build-url');

admin.initializeApp(functions.config().firebase);


exports.notifyChannel = functions.database.ref('/news/{what}/{uid}')
  .onCreate(event => {
    const config = functions.config().telegram;
    const url = buildUrl('https://api.telegram.org', {
      path: `bot${config.bot.token}/sendMessage`,
      queryParams: {
        chat_id: config.channel.chat_id,
      }
    });

    return request({
      method: 'POST',
      uri: url,
      resolveWithFullResponse: true,
      body: {
        text: event.data.val().content
      },
      json: true
    }).then(response => {
      if (response.statusCode === 200) {
        return response.body.id;
      }
      throw response.body;
    }
  );
});

Essa função é bem simples; basicamente, em qualquer evento de onCreate ela é chamada, então faço uma chamada POST na API do Telegram com o nome do canal, token do bot e conteúdo, que, no caso, é o texto da notícia.

Pregunta

E como os itens são salvos no Firebase?

Resposta: Recentemente, o Firebase lançou uma API para acessar a SDK usando Python, então eu escrevi um item pipeline chamado scrapy-firebase que usa essa API para escrever no banco de dados do Firebase, a cada item coletado do Scrapy, o método process_item do pipeline é chamado, e nesse método é salvo o item no Firebase.

class FirebasePipeline(BaseItemExporter):

    def load_spider(self, spider):
        self.crawler = spider.crawler
        self.settings = spider.settings

    def open_spider(self, spider):
        self.load_spider(spider)

        configuration = {
            'credential': credentials.Certificate(filename),
            'options': {'databaseURL': self.settings['FIREBASE_DATABASE']}
        }

        firebase_admin.initialize_app(**configuration)
        self.ref = db.reference(self.settings['FIREBASE_REF'])

    def process_item(self, item, spider):
        item = dict(self._get_serialized_fields(item))

        child = self.ref.child('/'.join([item[key] for key in self.keys]))
        child.set(item)
        return item

Próximos passos

Ao mesmo tempo em que eu notifico o canal do Telegram, estou usando o Cloud Natural Language API para classificar a notícia, e, em seguida, salvo no BigQuery. Após algum tempo, acredito que será possível usar o BigQuery para determinar quais trechos, quando e o quê costuma dar mais problemas à rodovia, através de data mining!

Código-fonte: https://github.com/skhaz/highway-overseer

Problema

por skhaz em 10 de July de 2017 às 00:00

June 28, 2017

Blog do PauloHRPinheiro

June 27, 2017

Bruno Cezar Rocha

The quality of the python ecosystem

[en]

Recently I talked about The Quality of The Python Ecosystem in "Caipyra" a very nice conference in Ribeirão Preto, Brazil.

Here are the slides (in English and also in Portuguese) and some pictures of the awesome event!

[pt]

Recentemente falei sobre a Qualidade do Ecossitema Python no evento "Caipyra"  em Ribeirão Preto, Brasil.

Aqui estão os slides (em inglês e Português) e também algumas fotos desse evento maravilhoso!


Slides

[en]


[pt]


Pictures

See full picture collection here: https://www.flickr.com/photos/rdegiovani/sets/72157685348056266/


Extra notes and updates:

Nick Coghlan pointed to those 2 links:

OPenShift.io has built-in safety and analytics tools 

https://developers.redhat.com/blog/2017/05/02/increasing-developer-confidence-and-reducing-development-risk-with-red-hat-openshift-io-analytics/


The recommendation engine is open source

https://github.com/fabric8-analytics

por Bruno Rocha em 27 de June de 2017 às 22:57

June 17, 2017

Flávio Coelho

Curso de introdução a criptomoedas - Aula 01

For the Portuguese speaking readers of this blog, I am starting an Introductory course on Cryptocurrencies and applications on the blockchain which is an online version of a standard classroom course I am starting now at FGV. This is the first lecture which is basically an intro to the topic and the structure of the course. The online version should have the main content of the lectures on

por Flavio Coelho (noreply@blogger.com) em 17 de June de 2017 às 16:50

June 16, 2017

Thiago Avelino

The legal robot and Artificial Intelligence for law!

Much has been said about robots that can read contracts. And the first image that comes to mind is this:

This is something very far and distant, complex and expensive. Right?

Not for Nuveo. We simplify the process!

Contrary to popular belief, this technology is much closer to our lives than it seems. Nuveo's software optimizes the work of lawyers or whoever needs to review contracts and track dates and milestones.

A 200-page contract can be summarized to 5 pages, with the details that really matter and need to be reviewed by the team – making sure that the review is taken on things that truly deserve attention.

Therefore, a review job that would take days or even weeks can be summed up in a few hours.

Do you want to know more about Nuveo? Talk to us!


The legal robot and Artificial Intelligence for law! was originally published in Nuveo on Medium, where people are continuing the conversation by highlighting and responding to this story.

por Avelino em 16 de June de 2017 às 00:19

June 04, 2017

Thiago Avelino

Conheça seu ambiente de trabalho

Aprender novas tecnologias (nesse caso linguagens de programação) não é um trabalho fácil, vai muito além de conhecer syntax da linguagem. Exige alguns desafios como entender porque a linguagem foi criada (existe), entender o eco sistema, como fazer deploy, como gerenciar ambiente de produção e etc.

Mantenha seu ambiente de trabalho organizado e com tudo a seu alcance

Pensando nessa introdução vou falar um pouco sobre conhecer o seu ambiente de trabalho antes de conhecer novas tecnologias (linguagens), é muito comum ver desenvolvedores querer usar sempre o que existe de mais novo no mercado (linguagem, framework, plugin, editor e etc) sem pensar no time de desenvolvimento envolvido, mas sera que isso realmente é a melhor coisa a se fazer para empresa que você trabalha? O nível de complexidade são diferente para pessoas, é extremamente comum ver um desenvolvedor (geralmente auto de data) aprender novas tecnologias muito rápido, mas nem todos são assim. Um dos trabalhos de ser CTO é saber dosar a dose do remédio para que todos os desenvolvedores esteja sempre na mesma página, isso envolve dizer alguns não momentâneos (não é fácil esse papel, mas é extremamente necessário).

Conhecer seu editor

É primordial você conhecer o editor que você trabalha, não adianta você querer usar o editor que esta em hype para a tecnologia X, use o que você realmente sabe usar. Principalmente na área de desenvolvimento de software muitos desenvolvedores fala de Emacs e Vim, você realmente precisa usar esses editores? Minha opinião é que você conheça, pois é importante conhece como outros desenvolvedores trabalha, mas leve isso como um novo caso de estudo não leve em paralelo com o estudo sobre a tecnologia que esta aprendendo, tenha foco no que você quer aprender, tempos tempo para aprender muita coisa na vida, basta ter paciência, perseverança e foco nos objetivos.

Pontos importantes para você conhecer do seu editor:

  • Teclas de atalhos: com o passar do tempo você percebe que seu dia começa ficar mais produtivo quando começa largar a dependência do mouse;
  • Uso de busca em código (não se prenda dentro do editor, conheça como fazer usando bash também);
  • Trabalhar com mais de um projeto na mesma janela do Editor (Hoje é extremamente comum dividir o que é frontend e backend, trabalhar com micros serviço e etc, com esse cenário sera necessário você se adaptar em trabalhar com mais de um projeto aberto pois você precisa implementar e/ou corrigir código em mais de um projeto), as maioria dos editores de hoje em dia lhe da suporte a isso, fique tranquilo;
  • Conhecer configuração do seu editor, como colocar um plugin novo, como configurar uma linguagem nova e etc.
Usei durante muitos anos TextMate até me dedicar a aprender VIM (sim, parei de estudar durante 6 mês outras tecnologias e me voltei para o estudo do VIM), isso me fez aprender muito mas é uma escolha, o que você quer aprender? X ou Y? Depois de anos usando VIM resolvi aprender usar Emacs e já estou a 2 anos usando ele como editor principal, ou seja, mudamos de opniões e temos disponibilidade de conhecer coisas novas.

Formas de debugar seu software

Saber uma linguagem de programação nova não basta saber a syntax e sim quais ferramentas ela lhe traz para debugar o software que esta desenvolvendo, seja print, breakpoint, gdb, pdb ou qual quer outra forma. Como desenvolvedor é extremamente importante software ser capaz de achar bugs dentro do software que você esta desenvolvendo ou dando manutenção (desenvolvido por outro desenvolvedor), se você não estudar a fundo como debugar software na sua linguagem você tera alguns problemas para achar o bug que seu software pode ter.

Logs, ambiente de produção precisa ser rastreável

Antes de falar de logs você precisa ter em mente as principais diferença de logprint.

Conhecer uma linguagem de programação nova é necessário saber como ela se comporta em produção (falando mais uma vez, não é só saber syntax), logar eventos do seu software é extremamente importante, colocar cor para identificar evento é muito importante para você não perder horas tendo que analisar um log, ou até mesmo jogar os logs coletados fora pois esta impossível de se fazer analise (infelizmente já vi mais de uma vez isso acontecendo).

Pratique (treine)

Todos atleta de alta performance precisa praticar (exaustivamente) para ficar bom o suficiente para assim competir. Em desenvolvimento de software não é diferente, você precisa praticar para conseguir desenvolver software sem ficar pensando "qual a teclada de atalho no meu editor mesmo?", "qual o nome daquele pacote que faz XYZ mesmo?" e etc. Se você tiver que ficar pensando nessas coisas como seu cérebro tem espaço pra saber qual o problema que você tem que resolver, pensar na melhor maneira da sua arquitetura? Não tem como pensar nessas coisas pois seu cérebro está concentrado em entender seu eco sistema e não resolver o problema, por isso devemos ter o eco sistema em nossa mente para ai sim depois pensar na solução do problema.

Quanto melhor você conhecer suas ferramentas melhor você será como desenvolvedor. Uma forma que eu faço para praticar é contribuir com projetos open source, desenvolver não é apenas escrever código e contribuição open source envolve mais relacionamento humana (remoto que é mais difícil ainda) do que código.

Se você não é bom em falar com pessoal é importante aprender by JavaMan

Considerações finais

Entenda seu ambiente de trabalho, você precisa dominar seu eco sistema para performar mais no seu dia a dia, deixe ele tão simples para o seu cérebro para você não precisar pensar mais nele. Trabalhe com uma tecnologia que lhe deixe feliz e faça com ela o que você realmente precisa fazer, não se engane buscando desculpas dentro de si mesmo para lhe justificar o que esta fazendo (aprendendo X ou Y). Mantenha seu foco em estudos que lhe traz evolução profissional e/ou pessoal.

por Avelino em 04 de June de 2017 às 17:16

May 27, 2017

Thiago Avelino

Parabéns pelos 10 anos de empresa Michel, não é nada fácil passar do primeiro ano de vida imaginei…

Parabéns pelos 10 anos de empresa Michel, não é nada fácil passar do primeiro ano de vida imaginei chegar aos 10 anos como vocês estão. Sucesso e que daqui 10 ano eu esteja lendo o blogpost de 20 anos…

por Avelino em 27 de May de 2017 às 16:54

May 26, 2017

Thiago Avelino

Browser Automation to query public base and automation of work

Following our articles talking about Nuveo products, today we want to highlight "Query".

But what is Nuveo Query anyway?

Query is nothing more than a sequence of jobs and searches on different web, internet databases run by the Nuveo Robot.

The product was built through a partnership between Nuveo and MCamilo (a Brazilian BPO company), after identifying some great opportunities in the insurance market.

In summary, Query is ready to perform any repetitive web search work and bring relevant data about people or companies according to their configuration… and then include them in a database, report or specific format defined by the customer that uses the product.

The first robot moment frightens, but it came to expedite the work

Let’s look at a real life example: imagine that a bank needs certain information before giving a loan to a company. The bank will need to search, extract and validate several pieces of information, of that company, in different databases or websites. Now you just have to configure the Nuveo software according to a certain schedule, define what information needs to be validated and Query — the Nuveo Robot software — will do the rest… and bring it all ready for a last check only. That happens in a few seconds. Isn´t that incredible?

Do you see Query running in your business? Contact us.


Browser Automation to query public base and automation of work was originally published in Nuveo on Medium, where people are continuing the conversation by highlighting and responding to this story.

por Avelino em 26 de May de 2017 às 16:01

May 24, 2017

Filipe Saraiva

LaKademy 2017

Foto em grupo do LaKademy 2017

E chegamos à 5ª edição do encontro latino-americano do KDE, o LaKademy. Nesse tempo todo foi perceptível o crescimento da comunidade na região, em especial no Brasil, ainda que mantendo o fluxo típico dos trabalhos voluntários onde participantes vem e vão de acordo com suas demandas.

Dessa vez o evento saiu das praias cariocas e adentrou um pouco mais para o interior do país, subindo o morro urbano de Belo Horizonte. Cidade aprazível conhecida pelas cachaças, queijos, cervejas artesanais, queijos, ladeiras e queijos, Belo Horizonte combina um ar cosmopolita, com diversas opções de lazer, culinária e mais, com um jeito cordial e solícito típico de seus moradores. Adorei a cidade e espero um dia agendar uma viagem que não seja a trabalho para lá.

As atividades do LaKademy ocorreram nas dependências do CEFET, do final de abril ao início de maio, em pleno feriadão do dia do trabalhador combinado a uma greve geral dias antes. Muitos que participaram do evento (eu incluso) defendiam as pautas da greve, mas não podíamos abandonar o evento após todo o investimento feito pelo KDE. Portanto, fica aqui meu mea culpa sobre esse tema. 🙂

A exemplo das demais edições do evento trabalhei bastante no Cantor, software matemático o qual sou mantenedor. Dessa vez as principais tarefas que desenvolvi podem ser resumidas em um grande esforço de triagem: revisões de patches pendentes, uma extensa revisão para fechar todos os bugs antigos e inválidos existentes, deixando abertos apenas aqueles que importam, e outra revisão nas tarefas pendentes, em especial naquelas que estavam abertas há quase um ano mas cujo os desenvolvedores responsáveis não haviam realizado qualquer movimentação durante o referido tempo.

No campo das funcionalidades, finalizei uma refatoração nos backends para apresentar a versão recomendada da linguagem de programação no Cantor. Como cada linguagem tem seu próprio planejamento, é comum que de uma versão para outra o backend do Cantor comece a se comportar de maneira inesperada ou mesmo deixe de funcionar (Sage, estou pensando em você). Essa funcionalidade apresenta a versão “recomendada” da linguagem para o backend do Cantor, significando que essa versão descrita foi testada e sabemos que funcionará bem com a ferramenta. Isso serve como um workaround para manter a sanidade do desenvolvedor enquanto suporta 11 backends diferentes.

Outra funcionalidade que trabalhei mas ainda não finalizei foi a adição de um seletor de backends LaTeX para usar no Cantor. Atualmente existem muitas opções de processadores LaTeX (pdflatex, pdftex, luatex, xetex, …), alguns deles com muitas opções adicionais. Isso aumentaria a versatilidade do Cantor e permitira que processadores modernos possam ser utilizados no software.

Além dessas funcionalidades houveram correções de bugs e auxílio ao Fernando Telles em algumas tarefas sobre esse software.

Outras tarefas que desenvolvi nessa edição, também a exemplo das demais, foram as relacionadas com o gerenciamento e promoção do KDE Brasil. Nelas, pesquisei como trazer de volta o feed do Planet KDE Português (que o Fred acabou desenvolvendo), atualização dos feeds automáticos nas nossas redes sociais, atualização da conta de e-mail que utilizamos para gerenciar nossas redes, port do site do LaKademy para bootstrap (que acho q o pessoal não vai utilizar pois estão migrando para WordPress) e uma pesada triagem das tarefas no workboard do KDE Brasil. Além de tudo isso, ainda tivemos a famosa reunião de promo onde discutimos ações de promoção para o país e região – tudo também documentado no workboard.

E claro, assim como trabalhamos muito e de forma muito intensa esses dias todos, o LaKademy também é um momento de reencontrar amigos e afetos e se divertir bastante entre um push e outro. É sempre reconfortante encontrar a galera inteira, e fica o convite para que os calouros apareçam sempre.

Uma falta da edição desse ano foi a ausência de não brasileiros – precisamos pensar em estratégias de termos latino-americanos de outros países participando do LaKademy. Seria ruim que o evento passasse a ser tão somente um Akademy-BR.

Filipe e Chicão

Para finalizar, deixo meu agradecimento à comunidade e minha disposição para continuar trabalhando para tornar a América Latina uma região cada vez mais importante para o desenvolvimento e futuro do KDE.

por Filipe Saraiva em 24 de May de 2017 às 13:38

May 23, 2017

Thiago Avelino

Application of artificial intelligence in the financial market, in Brazil

Artificial Intelligence has a number of financial applications and we’re going to talk about it them.

Boost is nothing more than an optimization of reconciliation processes made in the Financial area.

In Brazil, Nuveo developed a solution for the department that generates most hidden costs to the company: payables and receivables.

Can you believe that the average rate charged by credit card companies is around 6%, for online trading? That’s right… and that´s aside from Chargebacks (Fraudulent Operations).

As an industry entrepreneurs and founders of a payment solutions company, we always tried to avoid these costs all the time, creating innovative alternatives to serve people that don’t have bank accounts and reducing our costs in the process as a whole.

The most common alternative, and that many of the small business owners use, is the so-called Banking Deposit. This is just the process of informing your bank account to your customer, asking for a wire transfer, and then requesting the payment slip. The next steps are to reconcile the payment and deliver the product or service.

What is the big problem in here? It’s how scalable the process is.

Imagine that a simple operation that has 10, 15 transactions per day. It is very easy to have an person running the reconciliation and delivering orders.

What if the business runs thousands of transactions per day, how many people will it take to make such the process?

Nuveo created a system that automatically reconciles customers’ slip images and the company´s bank statements. The robot interprets the image information and matches it with your bank statement, releasing the customer’s order through the ERP system.

With this technology, it is now possible to automate the deposit reconciliation process and significantly reducing operating costs — and also serving a wider variety of customers, mainly the ones who do not have bank accounts.

Do you see Nuveo working for you? Contact us!


Application of artificial intelligence in the financial market, in Brazil was originally published in Nuveo on Medium, where people are continuing the conversation by highlighting and responding to this story.

por Avelino em 23 de May de 2017 às 01:48

May 05, 2017

Magnun Leno

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 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 03, 2017

Humberto Rocha

Desbravando o pygame 2 - Desenhando na Tela

Ao longo de um jogo estamos constantemente desenhando na tela. E agora que já sabemos criar um programa em pygame é hora de começar a desenhar.

Plano de desenho

Voltando nas aulas de matemática do ensino fundamental fomos apresentados ao plano cartesiano. O plano cartesiano e um espaço bidimensional orientado pelos eixos x e y onde x aumenta para o lado direito e diminui para o lado esquerdo enquanto y aumenta para cima e diminui para baixo:

plano cartesiano

No pygame a coisa muda um pouco, o plano de desenho consiste também de dois eixos onde x aumenta para a direita e diminui para a esquerda, entretanto o y aumenta para baixo e diminui para cima, o inverso do plano cartesiano, sendo a área visível da tela um espaço que parte de do ponto x=0 e y=0 no topo esquerdo da tela até o tamanho determinado no comando pygame.display.set_mode((x, y)):

plano de desenho

Desenhando

Crie um arquivo draw.py com o seguinte conteúdo:

# -*- coding: utf-8 -*-

import time

import pygame

# definindo cores
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

pygame.init()

screen = pygame.display.set_mode((640, 480))
# carregando fonte
font = pygame.font.SysFont(None, 55)

pygame.display.set_caption('Olá mundo')

# preenchendo o fundo com preto
screen.fill(BLACK)

# desenhando na superfície 
pygame.draw.line(screen, WHITE, [10, 100], [630, 100], 5)
pygame.draw.rect(screen, BLUE, [200, 210, 40, 20])
pygame.draw.ellipse(screen, RED, [300, 200, 40, 40])
pygame.draw.polygon(screen, GREEN, [[420, 200], [440, 240], [400, 240]])

# atualizando a tela
pygame.display.flip()

time.sleep(5)

# preenchendo o fundo com preto
screen.fill(BLACK)

# definindo o texto
text = font.render('pygame', True, WHITE)
# copiando o texto para a superfície
screen.blit(text, [250, 200])

# atualizando a tela
pygame.display.flip()

time.sleep(5)

Iniciamos nosso programa definindo os valores de cores para serem usados mais a frente. Em seguida inicializamos o pygame como feito anteriormente e carregamos uma fonte com pygame.font.SysFont para ser usada para escrever na tela.

Após preencher a superfície screen com a cor preta utilizamos algumas funções de desenho disponíveis em pygame.draw:

  • line: desenha uma linha recebendo como parâmetros a superfície seguida da cor, uma lista com a coordenada de início e e uma lista com a coordenada de fim e a espessura da linha em pixels.
  • rect: desenha um retângulo recebendo como parâmetros a superfície seguida da cor e uma lista com a posição inicial em x, posição inicial em y, largura, altura.
  • ellipse: desenha uma circunferência recebendo como parâmetros a superfície seguida da cor e uma lista com a mesma estrutura do rect.
  • polygon: desenha um polígono recebendo como parâmetros a superfície seguida da cor e uma lista de listas com as coordenadas dos vértices do polígono.

Em seguida pygame.display.flip() atualiza toda a tela o conteúdo desenhado previamente na superfície screen, seguido de um intervalo de 5 segundos.

Passado o tempo do sleep, preenchemos a superfície com a cor preta e definimos o texto que será escrito na tela com font.render, passando como primeiro parâmetro a string pygame, em segundo o valor True indicando para usar antialias que suaviza o contorno do texto e em terceiro o branco como cor. Este método cria uma superfície intermediária com o texto que será passado para nossa superfície screen através do método blit que recebe a superfície que será copiada e seu posicionamento em screen.

Por fim, atualizamos a tela com o novo conteúdo, esperamos mais 5 segundos e finalizamos nosso programa.

Concluindo

Existem diversas formas de se desenhar na tela com pygame, você pode ver estes e outras funções de desenho com mais detalhes na documentação da biblioteca.

O código deste capítulo encontra-se disponível no repositório desbravando-pygame.

por Humberto Rocha em 03 de February de 2017 às 00:00

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

Humberto Rocha

Desbravando o pygame 1 - Conhecendo a Biblioteca

Desenvolver jogos é um lugar comum dentre muitas das pessoas que começam a estudar programação. Comigo não foi diferente, mesmo não seguindo a carreira de desenvolvimento de jogos esta sempre foi uma área que me chamou a atenção.

Estou criando esta série para aprender mais sobre desenvolvimento de jogos utilizando como ferramenta o pygame e para compartilhar minhas descobertas no processo. Irei desbrava-lo partindo de seus princípios básicos até a criação de um pequeno jogo pong single player.

Para isso, usarei programação procedural com o objetivo de atingir com mais facilidade os iniciantes em programação, finalizando com a refatoração para introduzir os conceitos de orientação a objetos para quem ainda não está familiarizado. Esta série foi elaborada tendo como base python 3.

Pygame

Pygame é um conjunto de módulos python para desenvolvimento de jogos. Ele adiciona funcionalidades em cima do SDL que é uma biblioteca multiplataforma que abstrai os componentes multimídia do computador como áudio e vídeo, permitindo o desenvolvimento com maior facilidade de programas que lidam com estes recursos como os jogos.

Instalação

Antes de prosseguir com a instalação você precisa do python instalado no sistema, caso não saiba como proceder veja os guias de instalação para linux, mac e windows.

O processo de instalação do pygame varia de acordo com o sistema operacional utilizado, no site oficial existem algumas opções de instalação descritas.

Para instalar em sistemas linux baseados em debian você precisa instalar antes os pacotes externos ao python através de:

$ sudo apt-get build-dep python-pygame

Em seguida, para instalar o pygame rode o seguinte comando em sua virtualenv:

$ pip install pygame

Ou no próprio sistema:

$ sudo pip install pygame

Olá mundo

Como de costume, ao aprender algo novo em programação vamos escrever um programa mínimo para verificar se está tudo funcionando e pronto para dar continuidade com o aprendizado.

Para isso criaremos um arquivo hello.py com o seguinte conteúdo:

# -*- coding: utf-8 -*-

import time

import pygame

pygame.init()

screen = pygame.display.set_mode([640, 480])

pygame.display.set_caption('Olá mundo')

screen.fill([0, 0, 0])

pygame.display.flip()

time.sleep(5)

Para rodar basta executar:

$ python hello.py

O resultado será este:

hello

Ainda está longe de ser um jogo, mas já temos alguns conceitos básicos importantes a serem comentados.

As três primeiras linhas são comuns a maioria dos programas escritos em python. Na primeira, definimos o tipo de codificação de caracteres usado pelo interpretador python que no caso foi utf-8. Esta linha não é necessária em python 3, mas para que as pessoas que estejam testando em python 2 não tenham problemas com acentuação prefiro manter este costume. As outras duas são a importação da biblioteca padrão time e da pygame que é onde reside toda a mágica.

O comando pygame.init() inicializa todos os módulos que necessitam de inicialização dentro do pygame.

Em pygame.display.set_mode criamos uma superfície para a nossa janela do tamanho 640x480 seguido de pygame.display.set_caption onde atribuímos o texto "Olá mundo" para o título da janela.

Com a janela criada, utilizamos o comando fill da janela screen para preenche-la com a cor preta. A cor é passada em formato de lista de três elementos que consistem dos valores do RGB que variam de 0 a 255. Troque os valores desta lista e rode novamente o programa para ver que a cor da janela mudar.

O comando pygame.display.flip representa um conceito importante do pygame e do desenvolvimento de jogos em geral. Quando usamos comandos para desenhar na tela, na verdade estamos desenhando na superfície criada em de memória que representa uma porção da nossa tela. Para copiar o conteúdo dela e mostra-lo na tela é preciso utilizar o flip que é análogo a você fazer o desenho para uma pessoa e ao terminar virar (flip) a superfície em que você estava desenhando para que ela veja. O flip atualiza a tela com o conteúdo desenhado na superfície screen.

Para finalizar o comando sleep da biblioteca time faz o programa esperar 5 segundos antes de finalizar. Caso contrário o programa fecharia antes que você possa ver o resultado.

Concluindo

Com isso, chegamos ao final deste primeiro capítulo apresentando a biblioteca e escrevendo nosso primeiro programa. O código deste capítulo encontra-se disponível neste repositório: desbravando-pygame.

Fique de olho nos próximos capítulos onde iremos descrever conceitos do desenvolvimento de jogos e sua aplicação dentro do pygame.

por Humberto Rocha em 15 de January de 2017 às 00:00

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

December 02, 2016

Humberto Rocha

Coding Dojo 101

Meu primeiro Coding Dojo aconteceu próximo ao meu primeiro contato com a linguagem Python na faculdade. Um colega de classe (Dirley) tinha acabado de voltar da Python Brasil muito animado, pois tinha visto uma palestra sobre o tema e queria colocar em prática com o pessoal da turma. Aquele dia foi muito divertido e talvez um dos mais importantes momentos que me levaram a mergulhar de cabeça na linguagem Python.

Assim como me trouxe para a linguagem e posteriormente para a comunidade, o dojo pode ser uma porta de entrada para muita gente na programação.

O que é o Coding Dojo?

Assim como nas artes marciais o dojo é um ambiente de aprendizado e este é um conceito que deve ser reforçado na cabeça dos participantes.

Além disso é um ambiente no qual é praticado o desenvolvimento orientado a testes ou TDD, a programação pareada e babysteps para resolver um desafio proposto pelo organizador.

A resolução do desafio não é obrigatória para um dojo de sucesso, o objetivo principal é e sempre será a diversão.

Este vídeo ilustra brevemente seu funcionamento:

Um dojo pode ser feito remotamente, mas o presencial é onde a mágica acontece de verdade.

Requisitos

Para organizar um dojo não é preciso de muita coisa:

  • Uma sala ou um ambiente com lugar para sentar
  • Somente 1 computador onde os participantes iram programar
  • Uma tela maior ou projetor para a platéia acompanhar a programação

Dinâmica

A dinâmica acontece seguindo este fluxo:

dinamica

O fluxo é composto de ciclos de 5 a 7 minutos, este tempo é flexível, porém para iniciantes não é recomendado que seja muito extenso para evitar que a pessoa fique travada e para estimular mais pessoas a participar (eu pessoalmente sempre uso 5 minutos).

O piloto é o único que programa e não deve ser interrompido pela platéia durante cada ciclo, onde realiza programação pareada com o copiloto, que é a única pessoa que tem permissão para falar, ajudar e dar opinião.

A platéia assiste e não fala até o final de cada ciclo. Algumas pessoas gostam de fazer pequenas pausas quando os testes passam para discutir algo com a platéia, porém quando os testes estiverem falhando não é permitido nenhuma interação da platéia com a dupla no comando.

No intervalo de cada ciclo a dupla explica sua linha de pensamento e o que está fazendo, o piloto vai para a platéia, o copiloto assume a posição de piloto e um voluntário (de preferência quem ainda não programou) assume o copiloto.

Isso se repete durante todo o dojo, até a solução do desafio ou até o tempo máximo estipulado que costuma variar de 40 minutos até 1 hora e meia, mais que isso costuma ficar muito cansativo.

Programação Pareada

A programação pareada é uma das metodologias utilizadas no dojo e ela consiste nada mais nada menos de duas pessoas trabalhando em conjunto na solução de um problema. Sempre um programando e o outro ajudando com o raciocínio, sugestões e dúvidas.

Desenvolvimento Orientado a Testes

Outra metodologia essencial é o TDD, que consiste em escrever testes antes do código para resolver o seu problema. Se todos os testes estiverem passando você não tem o que programar, portanto é preciso escrever mais um teste que valide algo que ainda não foi implementado para que o teste falhe e lhe obrigue a escrever código para fazer aquele teste passar.

Babysteps

Escrever os testes primeiro é um grande desafio para quem não possuí este costume, as pessoas já querem testar a solução do problema de cara e sair resolvendo tudo o que leva a uma experiência ruim do TDD. Você possuí dificuldade em fazer os testes passarem? Provavelmente você está testando muita coisa em um único teste.

A ideia do babysteps é andar a "passos de bebê", quebrando seu problema em pequenos passos facilitando a resolução e a compreensão para o copiloto e para a platéia.

Finalizando

Ao término de um dojo é comum finalizar com um levantamento dos prós (o que foi legal), contras (o que não foi tão legal e poderia melhorar), sugestões (tanto de tema quanto de melhorias para o dojo) e o registro dos participantes.

Outro costume importante é disponibilizar o que foi produzido publicamente para consulta dos participantes e para a comunidade como um todo.

Ferramentas

Para auxiliar na experiência do dojo existem diversas ferramentas, a seguir apresentarei algumas das que eu utilizo:

Editor de texto

Eu sou um usuário de VIM e adoro utiliza-lo. Porém, a não ser que seja um dojo de VIM não é uma ferramenta muito recomendada pois pode causar frustração nas pessoas que não o utilizam.

No dojo eu gosto de utilizar o sublime text, por ser direto ao ponto e oferecer o code highlighting sem ter que configurar nada. Mas caso não possua a licença do sublime exitem outros bons editores gratuitos como o Atom.

Biblioteca de testes

Na realização dos testes costumo utilizar a biblioteca padrão de testes do Python, a unittest em conjunto com a biblioteca nose para facilitar o processo de rodar os testes.

Para escrever testes com a unittest basta criar uma classe que extende unittest.TestCasse:

import unittest

def incremento(x):
    return x + 1

class MeuTeste(unittest.TestCase):
    def testa_incremento(self):
        self.assertEqual(incremento(3), 4)

O exemplo acima implementa um método em MeuTeste que verifica se a função incremento com o parâmetro 3 retorna o valor esperado 4.

Rodando os testes com o nose:

$ nosetests arquivo.py

Ele buscará em seu arquivo todas classes que extendem unittest.TestCase e rodar os testes automaticamente.

Semáforo

Para saber se os testes estão passando teríamos que ficar rodando o comando de teste em toda alteração no código. Com um semáforo automatizamos este processo além de rodar os testes sempre que o arquivo é salvo disponibilizando um feedback visual bem interessante.

O semáforo que eu gosto de utilizar é o dose do Danilo Bellini:

dose

Ele funciona tanto com python quanto com outras linguagens além de integrar bem com o nose, basta rodar o comando:

$ dose nosetests arquivo.py

Concluindo

No dojo o mais importante é se divertir e aprender, estas são apenas algumas dicas a partir da minha experiência. Sintam-se a vontade para adaptar, criar e subverter tudo que foi descrito aqui e poste o que funcionou e o que não deu muito certo nos comentários.

Para quem estiver curioso sobre os códigos produzidos no dojo ou quiser sugestão de temas dê uma olhada no repositório do Grupy-DF que é onde eu e a comunidade Python do Distrito Federal mantemos nosso registro dos dojos passados.

E para quem é da região do DF e ainda não conhece nossa comunidade, estão todos convidados a participar. O site com todos os contatos da comunidade é df.python.org.br.

por Humberto Rocha em 02 de December de 2016 às 00:00

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