Planeta PythonBrasil

PythonBrasil[10]

May 05, 2016

PythonClub

GitHub Pages com Pelican e Travis-CI

Publicado originalmente em: df.python.org.br/blog/github-pages-com-pelican-e-travis-ci

Olá pessoal!

Estou fazendo esta postagem para ajudar quem quer criar seu site no GitHub Pages usando Pelican para a criação das páginas e o Travis-CI para automatizar a tarefa de geração e publicação.

Este guia assume que o leitor possua conta no GitHub e no Travis-CI e tenha familiaridade com o ambiente python. A versão do pelican utilizada ao elaborar esta publicação foi a 3.6.

O GitHub Pages

O GitHub Pages é uma funcionalidade gratuita do GitHub para hospedar conteúdo estático (html, css, js e imagens) e publicar através de um sub-domínio de github.io ou até mesmo de um domínio customizado. É baseado em seu funcionamento que iremos guiar nossos próximos passos.

Resumidamente existem duas formas de se criar uma página pelo gh pages:

1 - Página de usuário/organização

Para este tipo de página crie um repositório com o nome usuario.github.io, onde usuario é o nome de usuário ou organização da conta em que o repositório será criado:

Repositório da página de usuário

O conteúdo a ser publicado deve ser colocado na branch master e os arquivos do pelican na branch pelican.

2 - Página de projeto

Para este tipo de página crie um repositório com o nome meuprojeto, onde meuprojeto é o nome desejado para o projeto que será publicado em usuario.github.io:

Repositório da página de projeto

O conteúdo a ser publicado deve ser colocado na branch gh-pages e os arquivos do pelican na branch pelican.

Para mais informações acesse o site oficial do GitHub Pages.

Pelican

O pelican é um gerador de site estático otimizado por padrão para criação de blogs. Utilizaremos aqui, para fins de demonstração, o modelo padrão do de blog seguindo o caminho de criação de página de usuário/organização, qualquer diferença do caminho de página de projeto será descrita quando necessário.

Para instalar o pelican basta rodar o comando:

$ pip install pelican==3.6

Para criar um projeto faça:

$ mkdir humrochagf.github.io
$ cd humrochagf.github.io
$ pelican-quickstart
Welcome to pelican-quickstart v3.6.3.

This script will help you create a new Pelican-based website.

Please answer the following questions so this script can generate the files
needed by Pelican.


> Where do you want to create your new web site? [.]
> What will be the title of this web site? Meu Blog
> Who will be the author of this web site? Humberto Rocha
> What will be the default language of this web site? [en] pt
> Do you want to specify a URL prefix? e.g., http://example.com   (Y/n) n
> Do you want to enable article pagination? (Y/n) y
> How many articles per page do you want? [10]
> What is your time zone? [Europe/Paris] America/Sao_Paulo
> Do you want to generate a Fabfile/Makefile to automate generation and publishing? (Y/n) y
> Do you want an auto-reload & simpleHTTP script to assist with theme and site development? (Y/n) y
> Do you want to upload your website using FTP? (y/N) n
> Do you want to upload your website using SSH? (y/N) n
> Do you want to upload your website using Dropbox? (y/N) n
> Do you want to upload your website using S3? (y/N) n
> Do you want to upload your website using Rackspace Cloud Files? (y/N) n
> Do you want to upload your website using GitHub Pages? (y/N) y
> Is this your personal page (username.github.io)? (y/N) y
Done. Your new project is available at /caminho/para/humrochagf.github.io

Inicialize um repositório neste diretório e suba os dados para a branch pelican:

$ git init
$ git remote add origin git@github.com:humrochagf/humrochagf.github.io.git
$ git checkout -b pelican
$ git add .
$ git commit -m 'iniciando branch pelican'
$ git push origin pelican

Para publicar o conteúdo na branch master é necessário o módulo ghp-import:

$ pip install ghp-import
$ echo 'pelican==3.6\nghp-import' > requirements.txt
$ git add requirements.txt
$ git commit -m 'adicionando requirements'
$ git push origin pelican

Publicando o blog:

$ make github
Primeira publicação do blog

Para publicar no caso da página de projeto altere o conteúdo da variável GITHUB_PAGES_BRANCH do makefile de master para gh-pages.

Agora que o nosso blog está rodando no gh pages vamos automatizar a tarefa de geração das páginas para poder alterar o conteúdo do blog e fazer novas postagens sem precisar estar um uma máquina com o ambiente do pelican configurado.

Travis-CI

O Travis-CI é uma plataforma de Integração Contínua que monta e testa projetos hospedados no github e será nossa ferramenta para automatizar a montagem das páginas do blog.

A Primeira coisa a ser feita é ir ao Travis-CI e habilitar seu repositório.

Habilitando repositório no travis

Em seguida vá nas configurações do repositório no travis e desabilite a opção Build pull requests para seu blog não ser atualizado quando alguém abrir um pull request e habilite o Build only if .travis.yml is present para que somente a branch que possuir o arquivo .travis.yml gerar atualização no blog.

Configurando remositório no travis

O próximo passo é criar uma Deploy Key para que o travis possa publicar conteúdo no github. Para isso gere uma chave ssh na raiz do repositório local:

$ ssh-keygen -f publish-key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in publish-key.
Your public key has been saved in publish-key.pub.

Criada a chave vamos cifrar usando a ferramenta Travis-CLI (certifique-se de que esteja instalada em sua máquina) para poder publicar em nosso repositório sem expor o conteúdo da chave privada:

$ travis encrypt-file publish-key
Detected repository as humrochagf/humrochagf.github.io, is this correct? |yes| yes
encrypting publish-key for humrochagf/humrochagf.github.io
storing result as publish-key.enc
storing secure env variables for decryption

Please add the following to your build script (before_install stage in your .travis.yml, for instance):

    openssl aes-256-cbc -K $encrypted_591fe46d4973_key -iv $encrypted_591fe46d4973_iv -in publish-key.enc -out publish-key -d

Pro Tip: You can add it automatically by running with --add.

Make sure to add publish-key.enc to the git repository.
Make sure not to add publish-key to the git repository.
Commit all changes to your .travis.yml.

Como dito no resultado do comando podemos adicionar a opção --add para já adicionar as informações no .travis.yml, porém, para evitar de sobrescrever algum comando que venha existir no seu arquivo é recomendado editar manualmente.

Em nosso caso iremos criar o arquivo:

$ touch .travis.yml

E adicionar o seguinte conteúdo:

sudo: false
branches:
  only:
  - pelican
language: python
before_install:
# troque a linha abaixo pelo resultado do comando:
# travis encrypt-file publish-key
# porém mantenha o final:
# -out ~/.ssh/publish-key -d
- openssl aes-256-cbc -K $encrypted_591fe46d4973_key -iv $encrypted_591fe46d4973_iv -in publish-key.enc -out ~/.ssh/publish-key -d
- chmod u=rw,og= ~/.ssh/publish-key
- echo "Host github.com" >> ~/.ssh/config
- echo "  IdentityFile ~/.ssh/publish-key" >> ~/.ssh/config
# substitua git@github.com:humrochagf/humrochagf.github.io.git
# pelo endereço de acesso ssh do seu repositório
- git remote set-url origin git@github.com:humrochagf/humrochagf.github.io.git
# Caso esteja montando a página de projeto troque master:master
# por gh-pages:gh-pages
- git fetch origin -f master:master
install:
- pip install --upgrade pip
- pip install -r requirements.txt
script:
- make github

Removemos em seguida a chave privada não cifrada para não correr o risco de publicar no repositório:

$ rm publish-key

ATENÇÃO: Em hipótese alguma adicione o arquivo publish-key em seu repositório, pois ele contém a chave privada não cifrada que tem poder de commit em seu repositório, e não deve ser publicada. Adicione somente o arquivo publish-key.enc. Se você adicionou por engano refaça os passos de geração da chave e cifração para gerar uma chave nova.

Agora adicionaremos os arquivos no repositório:

$ git add .travis.yml publish-key.enc
$ git commit -m 'adicionando arquivos do travis'
$ git push origin pelican

Para liberar o acesso do travis adicionaremos a deploy key no github com o conteúdo da chave pública publish-key.pub:

Adicionando a deploy key no github

Pronto, agora podemos publicar conteúdo em nosso blog sem a necessidade de ter o pelican instalado na máquina:

Fazendo a primeira postagem

Que o travis irá publicar para você:

Blog com a primeira postagem

Caso você tenha animado de criar seu blog pessoal e quer saber mais sobre pelican você pode acompanhar a série do Mind Bending sobre o assunto.

por Humberto Rocha em 05 de May de 2016 às 00:46

May 02, 2016

Álvaro Jüsten

Dados Tabulares: A Maneira Pythônica

Já venho há algum tempo trabalhando na biblitoeca rows, que facilita demais o acesso a dados tabulares, não importa o formato do arquivo (CSV, XLS, XLSX, HTML, dentre outros). No último sábado (30 de abril) fiz uma palestra no encontro PythOnRio sobre o projeto e a galera se amarrou! Além da biblioteca, também mostrei como usar a command-line interface, que uso diariamente para pequenas análises e conversão de dados.

Confira o vídeo:

E os slides:

Veja também a versão em HTML dos slides.

O projeto é software livre e temos várias issues abertas, entra lá no repositório no GitHub e contribui! :)

por noreply@blogger.com (Álvaro Justen "Turicas") em 02 de May de 2016 às 16:27

Christiano Anderson

Treinamento de MongoDB presencial em Porto Alegre

Treinamento MongoDB

Treinamento MongoDB

O tão esperado treinamento de MongoDB acabou de sair do forno! Foram meses de trabalho para elaborar toda didática, material e exercícios. Finalmente deu certo e abrimos a primeira turma para o fim de maio.

O treinamento tem um total de 40 horas, é totalmente mão na massa e o principal objetivo é formar especialistas em MongoDB, com um enfoque em desenvolvimento e data science. Para desenvolver esse treinamento, aproveitei as melhores experiências que tive como consultor de MongoDB nos últimos anos, foram diversas implementações em clientes como Easy Taxi, Petrobras, Mercado Livre, entre outros.

Percebi que o mercado procurava não apenas um treinamento simples de MongoDB, mas que ajudasse a pensar de forma não relacional, já que schema design é o maior ponto de falha nessas implementações. Juntei um pouco de Python (para mostrar como desenvolver usando MongoDB) e algumas pitadas de data science, algo que tenho trabalhado diariamente.

Embora os exemplos sejam em Python, o mesmo pode ser aplicado a qualquer linguagem de programação.

Com tudo isso, esse treinamento não é apenas para formar novos ninjas em MongoDB, mas formar profissionais aptos a uma nova realidade que exige arquitetura de dados, desenvolvimento e data science.

Depois de Porto Alegre, vamos lançar esse mesmo treinamento em outras cidades!

Confira toda programação em http://even.tc/curso-mongodb e faça já sua inscrição, são poucas vagas!

Faça sua inscrição!!

Conteúdo programático

  • Módulo conceitual
    • Bancos não relacionais
    • Teorema de CAP
    • Outros bancos não relacionais
    • O que é MongoDB
    • História
    • Como pensar de forma Não Relacional
    • Onde usar NoSQL
    • Onde não usar NoSQL
  • Módulo introdutório
    • Instalação do MongoDB
    • O conceito de documentos
    • Tipos de instalação
    • Demonstração modo Standalone
    • Demonstração modo Réplica Set
    • Demonstração modo Sharding
    • Definições (Database, Coleção, Documentos)
  • Módulo CRUD
    • Inserindo documentos
    • Consultando documentos
    • Atualizando documentos
    • Removendo documentos
  • Módulo filtros avançados
    • Operadores de consulta
    • Consultando subdocumentos
    • Arrays
    • Filtros avançados
  • Módulo índices
    • Índices básicos
    • Índices compostos
    • Índices textuais
    • Índices geográficos
    • Índices com expire (TTL)
  • Módulo Aggregation Framework
    • Operador de pipeline
    • Operador $group
    • Operador $match
    • Operador $unwind
    • Demais operadores
    • Exemplos de uso
  • Módulo Replica Set
    • Criando um réplica set
    • Conceito de primário, secundário, secundário
    • Topologia recomendada
    • O uso ou não de árbitro
    • Limitações
  • Módulo Sharding
    • Criando um cluster
    • Usando réplica sets
    • Componentes do sharding
    • Escolhendo uma shard key corretamente
    • Tipos de shard key
    • Limitações
    • Importação de um grande volume de dados
  • Módulo desenvolvimento
    • Estabelecendo conexão com Python
    • Inserindo documentos em Python
    • Consultado documentos em Python
    • Atualizando documentos em Python
    • Removendo documentos em Python
    • Exercícios práticos
  • Módulo Arquitetura
    • Estruturando documentos
    • Limitações
    • Normalização vs Desnormalização
    • Erros mais comuns
    • O que não fazer
    • Boas práticas
    • Analisando um sistema de locadora
  • Módulo administração
    • Boas práticas no servidor de produção
    • Tuning
    • Como identificar gargalos
    • Analisando índices
    • Analisando performance geral do servidor
    • Autenticação e autorização
  • Módulo monitoramento
    • Usando o Cloud Manager
    • Como monitorar com Nagios
  • Módulo backup
    • Estratégias de backup
    • Estratégias de restore
  • Módulo bônus
    • Usando MongoDB e banco relacional na mesma aplicação
    • Outros bancos não relacionais
    • Dicas de como utilizar MongoDB em ambiente Amazon AWS
    • Dicas de data science, como processar dados usando MongoDB;

Para informações sobre locais, datas e condições, acesse o link abaixo:

Faça sua inscrição!!

O post Treinamento de MongoDB presencial em Porto Alegre apareceu primeiro em Christiano Anderson.

por Christiano Anderson em 02 de May de 2016 às 13:50

April 30, 2016

PythonClub

Sites Estáticos com Lektor

Publicado originalmente em: humberto.io/2016/4/sites-estaticos-com-lektor

Faz pelo menos 4 anos que eu ensaio para montar um blog, e nessa brincadeira já montei alguns, mas quando chegava na hora de criar o meu eu nunca conseguia publicar.

Inicialmente com ferramentas de publicação como wordpress o problema era a dificuldade de customizar e o tanto de coisa que vinha junto que eu não ia usar mas ficava me tirando a atenção. Em seguida com o GitHub Pages eu descobri o Pelican por indicação do Magnun Leno e comecei a fazer muita coisa com ele, mas da mesma forma que eu ganhei em liberdade de customização, o processo autoral é o mesmo de desenvolvimento, e como descrito no subtitulo do blog, meu lado cientista, pythonista e curioso ficava ali me cutucando para melhorar o site ao invés de escrever conteúdo.

Eis que em uma conversa no grupo de telegram da comunidade python me citam o Lektor e aí começou a aventura.

Lektor?

Lektor é um gerenciador de conteúdo estático criado por Armin Ronacher (sim, o criador do flask) que permite a criação de websites a partir de arquivos de texto.

Porque usar?

Como descrito no próprio site ele bebeu das fontes dos CMS`s, dos frameworks e dos geradores de site estático e chegou em algo que eu considero um ponto de equilíbrio entre eles, e que nos leva as seguintes vantagens:

  • Estático: O site final é totalmente estático, o que permite sua hospedagem em qualquer lugar;
  • CMS: Uma interface de produção de conteúdo que roda localmente e tira a necessidade de entender programação para poder produzir conteúdo. (no meu caso me tira do mundo do código e me deixa focar no conteúdo);
  • Framework: Ele possuí um sistema de models em arquivos de texto e um sistema de templates que usa Jinja2 que cria um ambiente familiar para quem já desenvolveu algo em django, flask e similares;
  • Deploy: O sistema de deploy dele é reduzido á uma configuração em um arquivo, o que permite a rápida publicação sem ficar dias aprendendo técnicas de deploy quando tudo que você quer no começo é colocar seu site no ar.

Instalação

A instalação do Lektor é bem direta:

$ curl -sf https://www.getlektor.com/install.sh | sh

Este comando instala diretamente no sistema, se você prefere instalar em sua virtualenv:

$ virtualenv venv
$ . venv/bin/activate
$ pip install Lektor

Esta forma é desencorajada pelos desenvolvedores pois o lektor gerencia virtualenvs internamente para instalação de seus plugins, portanto caso seja desenvolvedor e quer ter mais controle sobre o lektor instale a versão de desenvolvimento e esteja pronto para sujar as mãos quando for preciso, e quem sabe até contribuir com o desenvolvimento do lektor:

$ git clone https://github.com/lektor/lektor
$ cd lektor
$ make build-js
$ virtualenv venv
$ . venv/bin/activate
$ pip install --editable .

Obs.: requer npm instalado para montar a interface de administração.

Criando o Site

Após a instalação para criar o seu site basta utilizar o comando de criação de projeto:

$ lektor quickstart

Ele irá te fazer algumas perguntas e criar um projeto com o nome que você informou.

Estrutura

Esta é a estrutura básica de um site gerado pelo lektor:

meusite
├── assets/
├── content/
├── templates/
├── models/
└── meusite.lektorproject
  • assets: Pasta onde ficam os arquivos .css, .js, .ico entre outros recursos estáticos;
  • content: Pasta onde ficam os arquivos que iram gerar as páginas do site, cada subpasta corresponde a uma página no site gerado;
  • templates: Pasta onde ficam os arquivos de template que definem a sua estrutura visual;
  • models: Pasta onde ficam os arquivos que definem a modelagem de dados;
  • meusite.lektorproject: Arquivo com as configurações gerais do site.

Executando localmente

Para rodar o site em sua máquina basta entrar no diretório criado e iniciar o servidor local:

$ cd meusite
$ lektor server

Com o servidor rodando acesse localhost:5000 para ver o resultado:

meusite

Acessando o Admin

Para acessar o admin clique na imagem de lápis no canto superior direito da página que você criou ou acesse localhost:5000

meusite-admin

Publicando o Site

Exitem duas maneiras de se fazer o deploy do site construído com o lektor, a manual, que é basicamente rodar o comando build e copiar manualmente os arquivos para o servidor:

$ lektor build --output-path destino

E a forma automática, que pode ser feita (neste caso para o GitHub Pages) adicionando a seguinte configuração no arquivo meusite.lektorproject:

[servers.production]
target = ghpages://usuario/repositorio

E rodando em seguida o comando:

$ lektor deploy

Obs.: O deploy faz um force push na branch master ou gh-pages dependendo do tipo de repositório, portanto, cuidado para não sobrescrever os dados de seu repositório. Mantenha o código fonte em uma branch separada, você pode dar uma conferida no meu repositório para ter uma idéia.

Para informações mais detalhadas você pode acessar a documentação do lektor e também ficar de olho nas próximas postagens.

por Humberto Rocha em 30 de April de 2016 às 15:00

April 24, 2016

PythonClub

Django Rest Framework Serialization

Eu resolvi estudar um pouco mais de DRF depois do tutorial do Hugo Brilhante na Python Brasil 11.

Obs: se você não sabe Django sugiro que leia este tutorial antes.

Este tutorial é a parte 1/6 de uma série de posts sobre DRF.

Obs: Tem coisas que é melhor nem traduzir. ;)

  • 0 - Quickstart
  • 1 - Serialization
  • 2 - Requests & Responses
  • 3 - Class based views
  • 4 - Authentication & permissions
  • 5 - Relationships & hyperlinked APIs
  • 6 - Viewsets & routers

Pra quem não sabe, para usar API Web usamos REST, no caso, Django Rest Framework, framework web do Django.

Nota: este tutorial não é exatamente igual ao do Hugo, é baseado nele. E baseado também em Tutorial 1: Serialization.

Então para criar a API, no meu caso, eu usei:

  • Ambiente: .venv
  • Projeto: myproject
  • App: core
  • Model: Person
  • Fields: first_name, last_name, email, active (boolean), created

img

Configurando um novo ambiente

$ virtualenv -p python3 .venv
$ source .venv/bin/activate
$ mkdir drf; cd drf
$ pip install django==1.9.5 djangorestframework==3.3.3
$ pip install django-filter drf-nested-routers
$ pip freeze > requirements.txt
$ django-admin.py startproject myproject .
$ python manage.py startapp core

Veja o meu requirements.txt

Django==1.9.5
django-filter==0.11.0
djangorestframework==3.3.3
drf-nested-routers==0.10.0

Step-0 Projeto inicial

Abra o arquivo settings.py e em INSTALLED_APPS acrescente

INSTALLED_APPS = (
    ...
    'rest_framework',
    'core',
)

Step-1 Serializer

models.py: Criando o modelo Person

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField(null=True, blank=True)
    active = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True, auto_now=False)

    class Meta:
        ordering = ['first_name']
        verbose_name = u'pessoa'
        verbose_name_plural = u'pessoas'

    def __str__(self):
        return self.first_name + " " + self.last_name

    full_name = property(__str__)

serializers.py: Criando PersonSerializer

Precisamos proporcionar uma forma de serialização e desserialização das instâncias de person em uma representação JSON.

$ cd core/
$ touch serializers.py

Edite

from rest_framework import serializers
from core.models import Person

class PersonSerializer(serializers.Serializer):
    pk = serializers.IntegerField(read_only=True)
    first_name = serializers.CharField(max_length=30)
    last_name = serializers.CharField(max_length=30)
    email = serializers.EmailField()
    active = serializers.BooleanField(default=True)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        """
        Create and return a new `Person` instance, given the validated data.
        :param validated_data:
        """
        return Person.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Person` instance, given the validated data.
        """

        instance.first_name = validated_data.get(
            'first_name', instance.first_name)
        instance.last_name = validated_data.get(
            'last_name', instance.last_name)
        instance.email = validated_data.get('email', instance.email)
        instance.save()
        return instance

A primeira parte da classe define os campos que serão serializados. Os métodos create() e update() criam e atualizam as instâncias, respectivamente, quando chamados.

Uma classe de serialização é similar a uma classe Form do Django, e inclui validações similares para os campos, tais como required, max_length e default.

Fazendo a migração

$ ./manage.py makemigrations core
$ ./manage.py migrate

Trabalhando com a serialização

Abra o shell do Django.

$ ./manage.py shell

Primeiro vamos criar uma pessoa.

>>> from core.models import Person
>>> from core.serializers import PersonSerializer
>>> from rest_framework.renderers import JSONRenderer
>>> from rest_framework.parsers import JSONParser

>>> person = Person(first_name='Paul', last_name='Van Dyke', email='paul@email.com')
>>> person.save()

>>> person = Person(first_name='Regis', last_name='Santos', email='regis@email.com')
>>> person.save()

Agora que já temos alguns dados podemos ver a serialização da última instância.

>>> serializer = PersonSerializer(person)
>>> serializer.data
# {'pk': 2, 'first_name': 'Regis', 'created': '2015-11-15T03:20:25.084990Z', 'last_name': 'Santos', 'email': 'regis@email.com', 'active': True}

Neste ponto nós traduzimos a instância do modelo em tipos de dados nativos do Python. Para finalizar o processo de serialização nós vamos renderizar os dados em json.

>>> content = JSONRenderer().render(serializer.data)
>>> content
# b'{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}'

A desserialização é similar.

>>> from core.models import Person
>>> from core.serializers import PersonSerializer
>>> from rest_framework.renderers import JSONRenderer
>>> from rest_framework.parsers import JSONParser
>>> from django.utils.six import BytesIO

>>> person = Person.objects.get(pk=1)
>>> serializer = PersonSerializer(person)
>>> content = JSONRenderer().render(serializer.data)
>>> stream = BytesIO(content)
>>> data = JSONParser().parse(stream)
>>> serializer = PersonSerializer(data=data)
>>> serializer.is_valid()
# True
>>> serializer.validated_data
# OrderedDict([('first_name', 'Paul'), ('last_name', 'Van Dyke'), ('email', 'paul@email.com'), ('active', True)])

Step-2 ModelSerializer

Nossa classe PersonSerializer está replicando um monte de informações que está contido no modelo Person.

Da mesma forma que o Django fornece Form e ModelForm, REST framework inclui as classes Serializer e ModelSerializer.

Vamos refatorar nosso arquivo serializers.py, que agora ficará assim:

from rest_framework import serializers
from core.models import Person

class PersonSerializer(serializers.ModelSerializer):

    class Meta:
        model = Person
        fields = ('pk', 'first_name', 'last_name','email', 'active', 'created')

Uma propriedade legal que a serialização tem é que você pode inspecionar todos os campos em uma instância serializer, imprimindo sua representação. Abra o shell do Django.

$ ./manage.py shell
>>> from core.serializers import PersonSerializer
>>> serializer = PersonSerializer()
>>> print(repr(serializer))
# PersonSerializer():
#     pk = IntegerField(label='ID', read_only=True)
#     first_name = CharField(max_length=30)
#     last_name = CharField(max_length=30)
#     email = EmailField(allow_blank=True, allow_null=True, max_length=254, required=False)
#     active = BooleanField(required=False)
#     created = DateTimeField(read_only=True)

É importante lembrar que as classes ModelSerializer não faz nenhuma mágica, são simplesmente um atalho para a criação das classes de serialização:

  • Os campos são definidos automaticamente.
  • Os métodos create() e update() são implementados por padrão de uma forma simplificada.

views.py: Criando views regulares usando nosso Serializer

Vamos criar uma view simples para visualizar os dados em json.

Edite o arquivo views.py

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from core.models import Person
from core.serializers import PersonSerializer

class JSONResponse(HttpResponse):
    """
    An HttpResponse that renders its content into JSON.
    """

    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

A raiz da nossa API será uma lista de todas as pessoas, ou podemos criar uma pessoa nova.

@csrf_exempt
def person_list(request):
    """
    List all persons, or create a new person.
    """
    if request.method == 'GET':
        persons = Person.objects.all()
        serializer = PersonSerializer(persons, many=True)
        return JSONResponse(serializer.data)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = PersonSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data, status=201)
        return JSONResponse(serializer.errors, status=400)

Note que nós queremos usar o POST mas não temos o CSRF Token, por isso usamos o @csrf_exempt.

Também vamos precisar visualizar os detalhes de cada pessoa. Assim podemos recuperar, atualizar ou deletar cada registro.

@csrf_exempt
def person_detail(request, pk):
    """
    Retrieve, update or delete a person.
    """
    try:
        person = Person.objects.get(pk=pk)
    except Person.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = PersonSerializer(person)
        return JSONResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = PersonSerializer(person, data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data)
        return JSONResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        person.delete()
        return HttpResponse(status=204)

Agora, vamos criar as urls. Crie um novo arquivo core/urls.py.

from django.conf.urls import url
from core import views

urlpatterns = [
    url(r'^persons/$', views.person_list),
    url(r'^persons/(?P<pk>[0-9]+)/$', views.person_detail),
]

E acrescente a seguinte linha em myproject/urls.py.

urlpatterns = [
    url(r'^', include('core.urls')),
    url(r'^admin/', include(admin.site.urls)),
]

Instalando httpie

Podemos usar o curl, mas o httpie é mais amigável, e escrito em Python.

$ sudo pip install httpie

Vamos usar o httpie digitando

$ http http://127.0.0.1:8000/persons/

HTTP/1.0 200 OK
Content-Type: application/json
Date: Sun, 15 Nov 2015 03:24:44 GMT
Server: WSGIServer/0.2 CPython/3.4.3
X-Frame-Options: SAMEORIGIN

[
    {
        "active": true, 
        "created": "2015-11-15T03:20:24.938378Z", 
        "email": "paul@email.com", 
        "first_name": "Paul", 
        "last_name": "Van Dyke", 
        "pk": 1
    }, 
    {
        "active": true, 
        "created": "2015-11-15T03:20:25.084990Z", 
        "email": "regis@email.com", 
        "first_name": "Regis", 
        "last_name": "Santos", 
        "pk": 2
    }
]

Veja os detalhes

$ http http://127.0.0.1:8000/persons/1/

Atenção: se você receber erro 301, muito provavelmente é porque você esqueceu da barra / no final da url.

Como seria em curl?

Assim

$ curl http://127.0.0.1:8000/persons/

[{"pk":1,"first_name":"Paul","last_name":"Van Dyke","email":"paul@email.com","active":true,"created":"2015-11-15T03:20:24.938378Z"},{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}]

GitHub: Se você quiser pode olhar meu GitHub, mas terá que ver os commits para ver os passos.

por Regis da Silva em 24 de April de 2016 às 21:00

Python Help

O estranho caso do else em loops

Uma das coisas que me chamou a atenção quando comecei a usar Python é o else. O seu uso “natural”, para definir um caminho alternativo para um if, não tem nada demais. O que é um pouco estranho é o fato de Python aceitar else em expressões de loop como for e while. Por exemplo, o código abaixo é perfeitamente válido:

# -*- coding:utf-8 -*-
import random
segredo = random.randint(0, 10)
for tentativa in range(1, 4):
    numero = input('Digite um número entre 0 e 9 (tentativa %d de 3):' % tentativa)
    if numero == segredo:
        print('você acertou!')
        break
else:
    print('você esgotou as suas tentativas')

Perceba que o else está alinhado com o for e não com o if. Neste caso, os comandos contidos nele somente serão executados se o loop não tiver sido encerrado por um break. O mesmo vale para o else em loops while. No exemplo abaixo, o else nunca será executado pois o loop é sempre encerrado por um break:

while True:
    break
else:
    print('nunca serei!')

Confesso que sempre tive uma certa dificuldade em lembrar o significado do else nesses casos, até porque raramente me deparo com eles. Até que certo dia assisti uma palestra em que o autor disse (não me recordo quem):

O else de loops em Python deveria ser chamado ‘nobreak’.

Pronto, nunca mais esqueci o significado do else em loops!:)


por Valdir Stumm Jr em 24 de April de 2016 às 20:38

April 23, 2016

PythonClub

Explicit is better than implicit

Esse post não é diretamente relacionado a desenvolvimento com Python, mas conta a história de uma das muitas experiências que passamos desenvolvendo e mostra como a filosofia e o mindset Python podem nos influenciar a tomar decisões melhores.

Contexto geral

Atualmente trabalho remotamente pela Toptal, uma empresa de consultoria em software com foco em trabalho remoto e que tem um processo seletivo bastante rígido para garantir uma qualidade acima da média para seus clientes (saiba mais sobre a Toptal aqui).

No time em que faço parte os papéis são bem definidos entre desenvolvedores front-end e back-end e faço parte da equipe de back-end, que usa principalmente Django nas aplicações. À medida que evoluímos e nos tornamos mais maduros como time, buscamos soluções que pudessem otimizar nosso processo de desenvolvimento.

Atualmente utilizamos CircleCI -- uma plataforma para integração e entrega contínuas -- para tarefas como rodar nossa suíte de testes, fazer a integração de nosso código, instanciar uma nova versão de nossos sistemas em um ambiente de staging e criar imagens Docker posteriormente colocadas em produção.

Melhorias

Nosso time constantemente reavalia processos, ferramentas e o resultado são discussões interessantes sobre como tornar nosso trabalho mais rápido e produtivo.

Recentemente começamos a utilizar um servidor NPM -- um dos mais usados gerenciadores de pacotes para Javascript -- privado para uma melhor separação de pacotes front-end, otimizando o tempo de build de assets de 47 para 25 segundos.

Na raiz do nosso projeto temos um package.json com o seguinte conteúdo:

{
  // [ ... ]
  "dependencies": {
    "cat": "^1.0.0",
    "front": "^1.0.0",
    "core": "^1.0.0",
  },
  // [ ... ]
}

Sendo que cat, front e core (renomeados para exemplificar) são pacotes mantidos por nós mesmos no NPM privado. Por padrão, se você lista o pacote com “^” (como por exemplo acima “^1.0.0”), o npm considera apenas o número que representa a major version, no caso o número 1, e fará o download da última versão que começa com 1.

Essa abordagem tem quatro pontos fracos:

  1. Ela pode quebrar seu código. Se pacote de terceiro atualizar, seu código pode não estar preparado para lidar com as novas funcionalidades adicionadas, principalmente porque libs evoluem tão rapidamente que se torna fácil acontecer uma atualização sem backwards compatibility.
  2. Você não sabe exatamente qual versão do pacote seu sistema está usando em produção. Para saber, você teria que acessar os servidores remotamente e executar o comando npm list, por exemplo (poderia fazer localmente também mas existe a possibilidade de que no momento em que ocorreu o deploy, aquele pacote estava em uma versão anterior à sua versão local).
  3. Você perde o controle de quando quer que seu sistema utilize a nova versão do pacote.
  4. Se você precisar fazer um rollback ou usar uma imagem antiga de seu sistema em produção, ainda assim ela vai utilizar a última versão do pacote, o que pode levar a mais dores de cabeça.

Problema

Recentemente tivemos um bug em produção, e uma mudança no pacote core resolveria. O que fazer com o sistema principal? Nada, não era necessária nenhuma alteração. Só precisaríamos gerar uma nova imagem Docker que ela seria montada do zero e no momento de instalar os pacotes npm, baixaria a última versão.

Bastava realizar rebuild na branch master no CircleCI, que assim que terminado ele trataria de enviar um webhook para o nossa ferramenta que cria imagens Docker. Nós utilizamos o seguinte padrão de nomenclatura dessas imagens:

myapp-production-<branch>-<sha[:7]>

Como não fizemos nenhuma alteração no sistema principal, o branch e o sha continuaram os mesmos.

Resumindo, nosso Docker recebeu um pedido de build para aquela branch e sha e, por padrão, primeiro procurou em seu cache de imagens se já existia alguma imagem pronta com aquele nome. O resultado foi que a mesma imagem, sem o hotfix, foi para produção (pois ela havia sido criada antes e no momento em que baixou os pacotes npm ainda não havia alterações no core).

Demoramos um pouco para perceber o problema, mas o suficiente para resolvê-lo sem que stakeholders percebessem.

Solução

Algum tempo depois discutimos e nós desenvolvedores back-end sugerimos a seguinte solução:

{
  // [ ... ]
  "dependencies": {
    "cat": "1.0.5",
    "front": "1.0.7",
    "core": "1.0.10",
  },
  // [ ... ]
}

Com essa abordagem:

  1. Você pode fazer rollback do seu código sem problemas pois o código antigo vai usar a versão antiga do pacote.
  2. Você tem controle sobre quando quer que seu sistema utilize a nova versão do pacote.
  3. Você sabe exatamente quais versões de pacotes seu sistema está utilizando, bastando abrir o packages.json.
  4. Caso uma nova versão quebre seu código, você pode voltar uma versão rapidamente até que o problema seja resolvido.

O problema que tivemos em produção não aconteceria caso tivéssemos utilizado a abordagem acima. Assim que os pacotes fossem atualizados, criaríamos uma pull request no repositório do sistema principal com as seguintes alterações:

diff --git i/package.json w/package.json
index eaae10d..5aa773b 100644
--- i/package.json
+++ w/package.json
@@ -9,7 +9,7 @@
   "dependencies": {
     "cat": "1.0.5",
     "front": "1.0.7",
-    "core": "1.0.10",
+    "core": "1.0.11",
   },

Após o merge, um novo build aconteceria no CircleCI, e um novo sha seria enviado via webhook. O Docker não encontraria nenhuma imagem com essa combinação de branch e sha e criaria uma nova do zero. Produção teria o hotfix e não haveria constrangimento.

Os desenvolvedores front-end não gostaram da ideia de ter que atualizar o arquivo toda vez que alguma dependência subisse de versão. Discutimos bastante e a última coisa que eu disse foi: “from the Zen of Python: explicit is better than implicit”.

Lição aprendida.

por Ivan Neto em 23 de April de 2016 às 02:00

April 22, 2016

Gustavo Niemeyer

Snappy Interfaces

As much anticipated, this week Ubuntu 16.04 LTS was released with integrated support for snaps on classic Ubuntu.

Snappy 2.0 is a modern software platform, that includes the ability to define rich interfaces between snaps that control their security and confinement, comprehensive observation and control of system changes, completion and undoing of partial system changes across restarts/reboots/crashes, macaroon-based authentication for local access and store access, preliminary development mode, a polished filesystem layout and CLI experience, modern sequencing of revisions, and so forth.

The previous post in this series described the reassuring details behind how snappy does system changes. This post will now cover Snappy interfaces, the mechanism that controls the confinement and integration of snaps with other snaps and with the system itself.

A snap interface gives one snap the ability to use resources provided by another snap, including the operating system snap (ubuntu-core is itself a snap!). That’s quite vague, and intentionally so. Software interacts with other software for many reasons and in diverse ways, and Snappy is a platform that has to mediate all of that according to user needs.

In practice, though, the mechanism is straightforward and pleasant to deal with. Without any snaps in the system, there are no interfaces available:

% sudo snap interfaces
error: no interfaces found

If we install the ubuntu-core snap alone (done implicitly when the first snap is installed), we can already see some interface slots being provided by it, but no plugs connected to them:

% sudo snap install ubuntu-core
75.88 MB / 75.88 MB [=====================] 100.00 % 355.56 KB/s 

% snap interfaces
Slot                 Plug
:firewall-control    -
:home                -
:locale-control      -
(...)
:opengl              -
:timeserver-control  -
:timezone-control    -
:unity7              -
:x11                 -

The syntax is <snap>:<slot> and <snap>:<plug>. The lack of a snap name is a shorthand notation for slots and plugs on the operating system snap.

Now let’s install an application:

% sudo snap install ubuntu-calculator-app
120.01 MB / 120.01 MB [=====================] 100.00 % 328.88 KB/s 

% snap interfaces
Slot                 Plug
:firewall-control    -
:home                -
:locale-control      -
(...)
:opengl              ubuntu-calculator-app
:timeserver-control  -
:timezone-control    -
:unity7              ubuntu-calculator-app
:x11                 -

At this point the application should work fine. But let’s instead see what happens if we take away one of these interfaces:

% sudo snap disconnect \
             ubuntu-calculator-app:unity7 ubuntu-core:unity7 

% /snap/bin/ubuntu-calculator-app.calculator
QXcbConnection: Could not connect to display :0

The application installed depends on unity7 to be able to display itself properly, which is itself based on X11. When we disconnected the interface that gave it permission to be accessing these resources, the application was unable to touch them.

The security minded will observe that X11 is not in fact a secure protocol. A number of system abuses are possible when we hand an application this permission. Other interfaces such as home would give the snap access to every non-hidden file in the user’s $HOME directory (those that do not start with a dot), which means a malicious application might steal personal information and send it over the network (assuming it also defines a network plug).

Some might be surprised that this is the case, but this is a misunderstanding about the role of snaps and Snappy as a software platform. When you install software from the Ubuntu archive, that’s a statement of trust in the Ubuntu and Debian developers. When you install Google’s Chrome or MongoDB binaries from their respective archives, that’s a statement of trust in those developers (these have root on your system!). Snappy is not eliminating the need for that trust, as once you give a piece of software access to your personal files, web camera, microphone, etc, you need to believe that it won’t be using those allowances maliciously.

The point of Snappy’s confinement in that picture is to enable a software ecosystem that can control exactly what is allowed and to whom in a clear and observable way, in addition to the same procedural care that we’ve all learned to appreciate in the Linux world, not instead of it. Preventing people from using all relevant resources in the system would simply force them to use that same software over less secure mechanisms instead of fixing the problem.

And what we have today is just the beginning. These interfaces will soon become much richer and more fine grained, including resource selection (e.g. which serial port?), and some of them will disappear completely in favor of more secure choices (Unity 8, for instance).

These are exciting times for Ubuntu and the software world.

@gniemeyer

por niemeyer em 22 de April de 2016 às 16:41

April 20, 2016

Gustavo Niemeyer

Snappy Changes

As announced last Saturday, Snappy Ubuntu Core 2.0 has just been tagged and made its way into the archives of Ubuntu 16.04, which is due for the final release in the next days. So this is a nice time to start covering interesting aspects of what is being made available in this release.

A good choice for the first post in this series is talking about how snappy performs changes in the system, as that knowledge will be useful in observing and understanding what is going on in your snappy platform. Let’s start with the first operation you will likely do when first interacting with the snappy platform — install:

% sudo snap install ubuntu-calculator-app
120.01 MB / 120.01 MB [================================================================] 100.00 % 1.45 MB/s

This operation is traditionally done on analogous systems in an ephemeral way. That is, the software has either a local or a remote database of options to install, and once the change is requested the platform of choice will start acting on it with all state for the modification kept in memory. If something doesn’t go so well, such as a reboot or even a crash, the modification is lost.. in the best case. Besides being completely lost, it might also be partially applied to the system, with some files spread through the filesystem, and perhaps some of the involved hooks run. After the restart, the partial state remains until some manual action is taken.

Snappy instead has an engine that tracks and controls such changes in a persistent manner. All the recent changes, pending or not, may be observed via the API and the command line:

% snap changes
ID   Status  ...  Summary
1    Done    ...  Install "ubuntu-calculator-app" snap

(the spawn and ready date/time columns have been hidden for space)

The output gives an overview of what happened recently in the system, whether pending or not. If one of these changes is unintendedly interrupted for whatever reason, the daemon will attempt to continue the requested change at the next opportunity.

Continuing is not always possible, though, because there are external factors that such a change will generally depend upon (the snap being available, the system state remaining similar, etc). In those cases, the change will fail, and any relevant modifications performed on the system while attempting to accomplish the defined goal will be undone.

Because such partial states are possible and need to be handled properly by the system, changes are in fact broken down into finer grained tasks which are also tracked and observable while in progress or after completion. Using the change ID obtained in the former command, we can get a better picture of what that changed involved:

% snap changes 1
Status ...  Summary
Done   ...  Download snap "ubuntu-core" from channel "stable"
Done   ...  Mount snap "ubuntu-core"
Done   ...  Copy snap "ubuntu-core" data
Done   ...  Setup snap "ubuntu-core" security profiles
Done   ...  Make snap "ubuntu-core" available
Done   ...  Download snap "ubuntu-calculator-app"
Done   ...  Mount snap "ubuntu-calculator-app"
Done   ...  Copy snap "ubuntu-calculator-app" data
Done   ...  Setup snap "ubuntu-calculator-app" security profiles
Done   ...  Make snap "ubuntu-calculator-app" available

(the spawn and ready date/time columns have been hidden for space)

Here we can observe an interesting implementation detail of the snappy integration into Ubuntu: the ubuntu-core snap is at the moment ~80MB, and contains the software bundled with the snappy platform itself. Instead of having it pre-installed, it’s only pulled in when the first snap is installed.

Another interesting implementation detail that surfaces here is the fact snaps are in fact mounted rather than copied into the system as traditional packaging systems do, and they’re mounted read-only. That means the operation of having the content of a snap in the filesystem is instantaneous and atomic, and so is removing it. There are no partial states for that specific aspect, and the content cannot be modified.

Coming back into the task list, we can see above that all the tasks that the change involved are ready and did succeed, as expected from the earlier output we had seen for the change itself. Being more of an introspection view, though, this tasks view will often also show logs and error messages for the individual tasks, whether in progress or not.

The following view presents a similar change but with an error due to an intentionally corrupted system state that snappy could not recover from (path got a busy mountpoint hacked in):

% sudo snap install xkcd-webserver
[\] Make snap "xkcd-webserver" available to the system
error: cannot perform the following tasks:
- Make snap "xkcd-webserver" available to the system (symlink 13 /snap/xkcd-webserver/current: file exists)

% sudo snap changes 2
Status  ...  Summary
Undone  ...  Download snap "xkcd-webserver" from channel "stable"
Undone  ...  Mount snap "xkcd-webserver"
Undone  ...  Copy snap "xkcd-webserver" data
Undone  ...  Setup snap "xkcd-webserver" security profiles
Error   ...  Make snap "xkcd-webserver" available to the system

.................................................................
Make snap "xkcd-webserver" available to the system

2016-04-20T14:14:30-03:00 ERROR symlink 13 /snap/xkcd-webserver/current: file exists

Note how reassuring that report looks. It says exactly what went wrong, at which stage of the process, and it also points out that all the prior tasks that previously succeeded had their modifications undone. The security profiles were removed, the mount point was taken down, and so on.

This sort of behavior is to be expected of modern operating systems, and is fundamental when considering systems that should work unattended. Whether in a single execution or across restarts and reboots, changes either succeed or they don’t, and the system remains consistent, reliable, observable, and secure.

In the next blog post we’ll see details about the interfaces feature in snappy, which controls aspects of confinement and integration between snaps.

@gniemeyer

por niemeyer em 20 de April de 2016 às 17:48

April 16, 2016

Gustavo Niemeyer

Snappy Ubuntu Core 2.0 is OUT

Ubuntu and Snappy community, it’s time to celebrate!

After another intense week and a long Saturday focused on observing and fine tuning the user experience, the development team is proud to announce that Snappy 2.0 has been tagged. As has been recently announced, this release of Snappy Ubuntu Core will be available inside Ubuntu proper, extending it with new capabilities in a seamless manner.

This is an important moment for the project, as it materializes most of the agreements that were made over the past year, and does so with the promise of stability. So you may trust that the important external APIs of the project (filesystem layout, snap format, REST API, etc) will not change from now on.

The features that went into this release are way too rich for me to describe in this post, but you may expect us to be covering the many interesting aspects of Snappy 2.0 in the coming weeks. Rich interfaces between snaps that control security and confinement, comprehensive observation and control of system changes, completion and undoing of partial system changes across restarts/reboots/crashes, macaroon-based authentication for local access and store access, preliminary development mode, a polished filesystem layout and CLI experience, modern sequencing of revisions, and so forth.

Still, the most remarkable aspect about this release to me is that it is a solid foundation. This release exports APIs and is constructed in a way to be proud of, and together with this team I will be delighted to spend the foreseeable future building a platform the world has never seen.

As a final note, I can’t thank the development team enough for the dedication they have put into the project over the past year, and specially over these last two weeks. You were the make it or break it of this project, and you made it.

Thank you!

@gniemeyer

por niemeyer em 16 de April de 2016 às 20:56

April 14, 2016

Filipe Saraiva

AAAI e o apoio ao acesso aberto na Inteligência Artificial

AAAI é a sigla para Association for the Advancement of Artificial Intelligence, sociedade científica voltada para congregar os pesquisadores das diferentes vertentes do que comumente chamamos inteligência artificial: inteligência computacional, métodos metaheurísticos, agentes e sistemas multiagentes, raciocínio lógico automatizado, IA forte, aplicações de IA, entre muitas outras linhas.

Apesar de pequena quando comparada a esses grandes “guarda-chuvas de sociedades científicas” como o são o IEEE e a ACM, a AAAI é uma sociedade de renome e respeito na comunidade. Fundada em 1979, teve como presidentes importantes nomes da área como Marvin Minsky e John McCarthy, além de contar com influentes pesquisadores na sua lista de filiados.

Algo que sempre admirei na AAAI é o apoio ao acesso aberto nas suas publicações. Todas as conferências organizadas pela sociedade (como a AAAI – Conference on Artificial Intelligence ou a IAAI – Innovative Applications of Artificial Intelligence) e mesmo aquelas apenas apoiadas (como a IJCAI –  International Joint Conference on Artificial Intelligence) tem seus proceedings disponibilizados na internet, livre de taxas e acessível a qualquer pessoa com uma conexão.

A AAAI não tem uma revista científica própria, mas apoia a JAIR – Journal of Artificial Intelligence Research, também de acesso aberto. Por outro lado a sociedade mantém uma revista que publica artigos científicos e textos relacionados com a associação, a AI Magazine, que segue o modelo delayed open access – cada número é disponibilizado imediatamente aos sócios e torna-se de acesso aberto 15 meses após a publicação.

Quem conhece as publicações listadas sabe que são de excelente qualidade, todas listadas como A1 ou A2 na classificação de periódicos e conferências da área de ciência da computação do Qualis CAPES.

Há um endereço para a biblioteca digital da AAAI que concentra links para os diferentes tipos de publicações, mas você pode preferir ir direto para o endereço das conferências, para o da AI Magazine ou do JAIR.

Sabemos que todo esse esforço para produção e disponibilização gratuita desses materiais tem seus custos, e uma forma de apoiar a continuação desse trabalho e política é se associando à AAAI. Em 2016 a sociedade lançou uma novidade muito bem-vinda para nós brasileiros e demais moradores de países em desenvolvimento: há uma taxa especial de associação, que reduz o valor de uma filiação regular de U$ 145,00 para U$ 20,00, e da filiação estudantil de U$ 75,00 para U$ 18,00. Saiba mais sobre essa e outras informações sobre como se associar na página AAAI Membership.

Para mim, as sociedades científicas tem papel fundamental nas políticas de acesso aberto. Faz sentido que as editoras científicas lutem contra essa forma de disponibilização de conteúdo, afinal elas são empresas e tentam de todas as formas manter o modelo de negócios que as deu tanto lucro, por mais que a tecnologia tenha mudado e os custos que elas tem hoje seja absurdamente inferior ao que era anos atrás. Já as sociedades científicas normalmente são grupos que não visam lucro, e além disso são as “fornecedoras” da principal mão de obra do modelo de publicação científica – os cientistas que escrevem, revisam, editam e gerenciam artigos são membros de diferentes sociedades de suas áreas. Esses grupos, portanto, podem criar políticas de acesso aberto para suas produções, sem ônus ao seus objetivos de congregar pesquisadores, qualificar e distribuir o conhecimento produzido por eles, e assim avançar a ciência produzida naquela área.

Me filiei à AAAI e espero apoiar e participar dessa sociedade por muitos anos. Torço para um dia termos mais sociedades científicas como esta.

por Filipe Saraiva em 14 de April de 2016 às 15:06

April 12, 2016

Aprenda Python

Faça programas simples

Vá por mim. Faça programas tão simples que: - Qualquer programador entenda. - Você se lembre do propósito dele quando lê-lo alguns meses (ou anos) mais tarde. Tenha clareza do objetivo a ser alcançado. Construa um sistema, ao invés de "um apanhado de programas". Conheça a forma de comunicação entre as diversas partes do sistema. Dê nomes que façam sentido, dentro de um contexto.

por Vinicius Assef (noreply@blogger.com) em 12 de April de 2016 às 13:01

April 10, 2016

Aprenda Python

Eu devo usar Python 2 ou Python 3?

Afinal, qual Python usar? Se você vai começar a estudar Python agora ou iniciar um projeto novo, escolha Python 3 e leia Por onde começar com Python? Nesse artigo vou citar alguns motivos para não usar Python 2, quais são as diferenças entre o Python 2 o Python 3 e como saber se as ferramentas que você pretende usar são compatíveis com Python 3. Python 2 já vem sendo chamado de "Python

por Vinicius Assef (noreply@blogger.com) em 10 de April de 2016 às 03:38

April 08, 2016

Aprenda Python

Pedir permissão ou pedir perdão?

Em Python costumamos ouvir que é [mais fácil pedir perdão do que pedir permissão (It's easier to ask for forgiveness than permission)](https://docs.python.org/3/glossary.html#term-eafp), mas o que isso realmente significa? O que vemos normalmente, conhecida como programação defensiva, é: import os.path if os.path.exists('meu_arquivo.txt'): with open('meu_arquivo.txt') as f:

por Vinicius Assef (noreply@blogger.com) em 08 de April de 2016 às 04:27

April 07, 2016

Aprenda Python

Por que desenvolver um sistema usando Baby Steps

O maior problema da maioria de nós, programadores, não é tecnologia. Nosso maior problema é método de trabalho. A maioria de nós, programadores, aprende sozinho, domina várias linguagens, frameworks e bibliotecas mas colhe os maus frutos de processos e métodos de trabalho que poderiam ser melhores. Muito melhores. Há alguns dias eu li a seguinte pergunta: > > > Eu desenvolvi um

por Vinicius Assef (noreply@blogger.com) em 07 de April de 2016 às 11:34

April 06, 2016

Aprenda Python

Non-technical leaders and IT projects

Why many non-technical project leaders fail? What could they do to change the game? Every software project should have a tech leader ------------------------------------------------ It sounds obvious and apparently happens everytime & everywhere, but it doesn't. Bad things happen when a non-technical person (or a "boss") has a seat on the technical leader's chair: - Be prepared for

por Vinicius Assef (noreply@blogger.com) em 06 de April de 2016 às 16:17

April 01, 2016

Programando Ciência

[EVENT] SciPy 2016

Hey scientist!
In July we will have one important event for the world community of scientific Python: the SciPy 2016! Do you want to know what will happen there, and how to be a part of it?

The 15th edition of the Scientific Computing with Python conference, SciPy 2016, will happen between July 11 and 17, on Austin, Texas, USA. It will consist of two days of tutorials, three days of talks, and two days of developer sprints.

At the conference, these topics will be approached:

  • Scientific Computing in Python;
  • Python in Data Science;
  • Earth and Space Science;
  • Engineering;
  • Medicine and Biology;
  • Social Sciences;
  • Special Purpose Databases;
  • Case Studies in Industry and Education;
  • Reproducibility.

Besides us, there are several nice communities and enterprises supporting the event:

The keynote speakers are also defined:

  • Andreas Klöckner, assistant professor from the Illinois University at Urbana-Champaign.
  • Hanna Wallach, researcher from Microsoft Research in New York, and professor from the Massachusetts University at Amherst.

The deadline for talk and poster proposals ends today, April 1st! Go for it and present your research! If you don’t want to present but want to help the event, you can also be a volunteer. Give them a help:)

That’s it scientist! Show up, learn more about Python and present your ideas to some of the greatest programmers of the Earth!
Gigaregards! See you next time!


Did you like this post? Please comment and share with your friends!
Want to download Programando Ciência codes? Go to our GitHub!
Make a donation for Programando Ciência!
Like us also on Facebook: www.facebook.com/programandociencia
I’m on Twitter! Follow me if you can! @alexdesiqueira


por alexandrejaguar em 01 de April de 2016 às 14:14

[EVENTO] SciPy 2016

Faaaaaaala cientista!
Em julho, teremos mais um evento importante para a comunidade mundial de Python científico: a SciPy 2016! Quer saber o que vai acontecer lá, e como participar?

A décima quinta edição da conferência de computação científica com Python, a SciPy 2016, acontecerá entre 11 e 17 de julho na cidade de Austin, no Texas, EUA. Serão dois dias de tutoriais, três dias de palestras e dois dias de desenvolvimento de software (os famosos sprints).

Os seguintes tópicos serão abordados na conferência:

  • Computação científica com Python;
  • Python em ciência de dados;
  • Ciências espaciais e da Terra;
  • Engenharia;
  • Medicina e biologia;
  • Ciências sociais;
  • Bancos de dados especiais;
  • Estudos de caso na indústria e educação;
  • Reproducibilidade.

Além de nós, há várias comunidades e empresas bacanas apoiando o evento:

Também já estão definidos os palestrantes principais:

  • Andreas Klöckner, professor assistente da Universidade de Illinois em Urbana-Champaign.
  • Hanna Wallach, pesquisadora na Microsoft Research em Nova Iorque, e professora na Universidade de Massachusetts em Amherst.

A submissão de palestras e pôsteres acaba hoje, primeiro de abril! Corre lá para apresentar sua pesquisa! Se não quiser se apresentar, também estão abertas as inscrições para voluntários. Dê uma forcinha:)

É isso aí cientista! Compareça, aprenda mais sobre Python e exponha suas ideias a alguns dos maiores programadores do planeta!
Um giga abraço! Até a próxima!


Gostou? Curta e compartilhe com seus amigos!
Quer baixar os códigos do Programando Ciência? Corre lá no nosso GitHub!
Faça uma doação pro Programando Ciência!
Curta a gente também no Facebook: www.facebook.com/programandociencia
Estou no Twitter! Siga-me se puder! @alexdesiqueira


por alexandrejaguar em 01 de April de 2016 às 13:35

Magnun Leno

Hack ‘n’ Cast v0.19 - Introdução à Eletrônica Digital

Uma breve introdução à eletrônica digital, passando pela sua antecessora até chegar aos flip-flops.

Baixe o episódio e leia o shownotes

por Magnun em 01 de April de 2016 às 03:01

March 31, 2016

Aprenda Python

Qual a forma mais pythônica de chamar uma função com parâmetros?

Outro dia surgiu uma pergunta interessante na lista Python Brasil. Ela era mais ou menos assim: > Dada uma função declarada assim: > > def func1(foo=None, bar=None): > > Qual é a forma mais pythônica de chamá-la? > > Assim: func1('abc', 'def') > > ou assim: func1(foo='abc', bar='def') > ? Eu achei a dúvida interessante e resolvi escrever a respeito. **Resposta versão TL;DR:** use o

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

March 26, 2016

Filipe Saraiva

Workshop de Software Livre 2016 – chamada de artigos e ferramentas

wsl2016-banner

Está aberta a chamada de trabalhos do Workshop de Software Livre (WSL), o evento acadêmico irmão do Fórum Internacional de Software Livre (FISL). Esse ano temos uma interessante novidade: além da já usual chamada de artigos, há também uma chamada específica para ferramentas livres.

O WSL publica artigos científicos com os mais variados temas de interesse das comunidades de software livre, como dinâmica social, gerenciamento, processos de desenvolvimento e motivações de contribuidores em comunidades, adoção e estudos de caso sobre software livre, aspectos jurídicos e econômicos, estudos sociais e históricos sobre o tema, e muito mais.

Esse ano, com a chamada de ferramentas, iremos publicar artigos que descrevem software, garantindo que o programa descrito atende a diversos critérios de qualidade e de adequação quanto a ser software livre.

Todos os artigos serão publicados no repositório de acesso aberto do WSL. Esse ano estamos trabalhando forte para termos um ISSN e DOI para as publicações.

O deadline é dia 10 de abril. Os artigos podem ser submetidos em português, inglês ou espanhol.

Saiba mais na página oficial do WSL.

por Filipe Saraiva em 26 de March de 2016 às 16:55

March 25, 2016

Aprenda Python

Nomenclatura para nomes em Bancos de Dados

Vou descrever a forma como eu dou nomes a objetos de Bancos de Dados. Essas regras funcionam para mim, mas não significa que vão funcionar para você. Introdução ---------- Eu não: - costumo usar prefixos nem sufixos; - costumo identificar tipo de dado nos nomes; Eu uso: - todos os nomes em minúsculas; - o caractere sublinhado como separador entre as palavras. Lembre-se: toda regra tem

por Vinicius Assef (noreply@blogger.com) em 25 de March de 2016 às 00:56

March 20, 2016

Programando Ciência

[EVENT] SciPy Latin America 2016

Hey scientist!
Have you ever thought about participating in the largest Latin America event about scientific Python, and connecting to the members of the coolest programming community of the world?

That’s right: this year will be easier for us Brazilians to go to SciPyLA! The event will be hosted in Florianópolis, the island of magic!

Registration will open soon, and the event will happen in May, 16-20. Do you want to present some stuff? Tutorials and talks can be submitted until April, 8. Go for it dude! Show how you use Python on your research, or present a tutorial about that tool you use:)

Besides us, there are several nice communities supporting the event:

Enjoy Floripa and know also the beaches, the city main market, the Conceição pond, among several other touristic points! Also, meet new contacts and have a beer (or a tea) with our organization friends, such as Ivan Ogawasara, Raniere Silva, and all the others.

That’s it scientist! Enjoy the opportunity to integrate yourself with many people and learn about the nicest scientific subjects, all of that using Python!
Gigaregards! See you next time!


Did you like this post? Please comment and share with your friends!
Want to download Programando Ciência codes? Go to our GitHub!
Make a donation for Programando Ciência!
Like us also on Facebook: www.facebook.com/programandociencia
I’m on Twitter! Follow me if you can! @alexdesiqueira


por alexandrejaguar em 20 de March de 2016 às 06:47

[EVENTO] SciPy Latin America 2016

Faaaaaaala cientista!
Já pensou em estar no maior encontro sobre Python científico da América Latina e aprender várias coisas novas em tutoriais e palestras, além de se conectar com os membros da comunidade de programação mais bacana do planeta?

É isso aí: esse ano vai ser mais fácil para nós brazucas comparecermos na SciPyLA! O evento acontecerá em Florianópolis, a ilha da magia!

As inscrições abrirão em breve, e a SciPyLA acontecerá entre 16 e 20 de maio. Quer se apresentar? A submissão de palestras e tutoriais está aberta até 08 de abril. Vai que é sua, marajá! Mostre como você usa Python na sua pesquisa, ou ministre um tutorial sobre aquela ferramenta que você usa:)

Além de nós, há várias comunidades e empresas bacanas apoiando a SciPyLA:

Aproveite Floripa e conheça também as praias, o mercado municipal, a lagoa da Conceição, entre diversos pontos turísticos! Também faça novos contatos e tome uma cerveja (ou um chá) com os amigos da organização, como o Ivan Ogawasara, o Raniere Silva, entre muitos outros.

É isso aí cientista! Aproveite a oportunidade de se integrar com várias pessoas e aprender sobre os mais variados assuntos científicos, utilizando Python!
Um giga abraço! Até a próxima!


Gostou? Curta e compartilhe com seus amigos!
Quer baixar os códigos do Programando Ciência? Corre lá no nosso GitHub!
Faça uma doação pro Programando Ciência!
Curta a gente também no Facebook: www.facebook.com/programandociencia
Estou no Twitter! Siga-me se puder! @alexdesiqueira


por alexandrejaguar em 20 de March de 2016 às 06:40

March 17, 2016

Aprenda Python

Uma visão sobre frameworks fullstack

Antes de começar ---------------- Esse artigo faz parte de uma série que estou produzindo sobre frameworks web em Python. Em breve escreverei outros. Leia [Qual é a diferença entre framework fullstack e microframework?](http://aprenda-python.blogspot.com/2016/02/qual-a-diferenca-entre-framework-fullstack-e-micro.html) antes de ler esse artigo, para entender o contexto. Introdução ----------

por Vinicius Assef (noreply@blogger.com) em 17 de March de 2016 às 06:22

Qual a diferença entre framework fullstack e microframework?

**Importante:** o termo "fullstack", quando referindo-se a frameworks web em Python, não inclui, necessariamente, tecnologias de frontend. Conceitos --------- Os frameworks fullstack vêm com todas as ferramentas (ou a maioria delas) necessárias para desenvolver o backend de uma aplicação Web, em Python: - Tratamento de requisição (request) HTTP; - Envio da resposta (response) a uma

por Vinicius Assef (noreply@blogger.com) em 17 de March de 2016 às 06:21

March 14, 2016

PythonClub

TDD com Python e Flask

Baseado na palestra que ofereci no encontro do Grupy-SP, em 12 de março de 2016. O código dessa atividade está disponível no meu GitHub.

A ideia desse exercício é introduzir a ideia de test driven development (TDD) usando Python e Flask — digo isso pois a aplicação final desse “tutorial” não é nada avançada, tampouco funcional. E isso se explica por dois motivos: primeiro, o foco é sentir o que é o driven do TDD, ou seja, como uma estrutura de tests first (sempre começar escrevendo os testes, e não a aplicação) pode guiar o processo de desenvolvimento; e, segundo, ser uma atividade rápida, de mais ou menos 1h.

Em outras palavras, não espere aprender muito de Python ou Flask. Aqui se concentre em sentir a diferença de utilizar um método de programar. Todo o resto é secundário.

1. Preparando o ambiente

Requisitos

Para esse exercício usaremos o Python versão 3.5.1 com o framework Flask versão 0.10.1. É recomendado, mas não necessário, usar um virtualenv.

Como o código é bem simples, não acho que você vá ter muitos problemas se utilizar uma versão mais antiga do Python (ou mesmo do Flask). Em todo caso, em um detalhe ou outro você pode se deparar com mensagens distintas se utilizar o Python 2.

Você pode verificar a versão do seu Python com esse comando:

$ python --version                                                                                    

Dependendo da sua instalação, pode ser que você tenha que usar python3 ao invés de python — ou seja, o comando todo deve ser python3 --version. O resultado deve ser esse:

Python 3.5.1

E instalar o Flask assim:

$ pip install Flask

O pip é um gerenciador de pacotes do Python. Ele vem instalado por padrão nas versões mais novas do Python. Dependendo da sua instalação, pode ser que você tenha que usar pip3 ao invés de pip — ou seja, o comando todo deve ser pip3 install Flask. Com esse comando ele vai instalar o Flask e qualquer dependência que o Flask tenha:

Collecting Flask
Collecting Jinja2>=2.4 (from Flask)
  Using cached Jinja2-2.8-py2.py3-none-any.whl
Collecting itsdangerous>=0.21 (from Flask)
Collecting Werkzeug>=0.7 (from Flask)
  Using cached Werkzeug-0.11.4-py2.py3-none-any.whl
Collecting MarkupSafe (from Jinja2>=2.4->Flask)
Installing collected packages: MarkupSafe, Jinja2, itsdangerous, Werkzeug, Flask
Successfully installed Flask-0.10.1 Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.4 itsdangerous-0.24

Arquivos

Vamos usar, nesse exercício, basicamente 2 arquivos:

  • app.py: onde criamos nossa aplicação web;
  • tests.py: onde escrevemos os testes que guiarão o desenvolvimento da aplicação, e que, também, garantirão que ela funcione.

2. Criando a base dos testes

No arquivo tests.py vamos usar o módulo unittest, que já vem instalado por padrão no Python.

Criaremos uma estrutura básica para que, toda vez que esse arquivo seja executado, o unittest se encarregue de encontrar todos os nossos testes e rodá-los.

Vamos começar escrevendo com um exemplo fictício: testes para um método que ainda não criamos, um método que calcule números fatoriais. A ideia é só entender como escreveremos testes em um arquivo (tests.py) para testar o que escreveremos no outro arquivo (app.py).

A estrutura básica a seguir cria um caso de teste da unittest e, quando executada, teste nosso método fatorial(numero) para todos os números de 0 até 6:

import unittest


class TestFatorial(unittest.TestCase):

    def test_fatorial(self):
        self.assertEqual(fatorial(0), 1)
        self.assertEqual(fatorial(1), 1)
        self.assertEqual(fatorial(2), 2)
        self.assertEqual(fatorial(3), 6)
        self.assertEqual(fatorial(4), 24)
        self.assertEqual(fatorial(5), 120)
        self.assertEqual(fatorial(6), 720)

if __name__ == '__main__':
    unittest.main()

Se você conhece um pouco de inglês, pode ler o código em voz alta, ele é quase auto explicativo: importamos o módulo unittest (linha 1), criamos um objeto que é um caso de teste do método fatorial (linha 4), escrevemos um método de teste (linha 6) e esse método se assegura de que o retorno de fatorial(numero) é o resultado que esperamos (linhas 5 a 11).

Agora podemos rodar os testes assim:

$ python testes.py

Veremos uma mensagem de erro, NameError, pois não definimos nossa função fatorial(numero):

E
======================================================================
ERROR: test_fatorial (__main__.TestSimples)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 7, in test_fatorial
    self.assertEqual(fatorial(0), 1)
NameError: name 'fatorial' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Tudo bem, a ideia não é brincar com matemática agora. Mas vamos criar essa função lá no app.py só para ver como a gente pode “integrar” esses dois arquivos — ou seja, fazer o tests.py testar o que está em app.py.

Vamos adicionar essas linhas ao app.py:

def fatorial(numero):
    if numero in (0, 1):
        return 1
    return numero * fatorial(numero - 1)

E adicionar essa linha no topo do tests.py:

from app import fatorial

Agora, rodando os testes vemos que a integração entre app.py e tests.py está funcionando:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Ótimo. Chega de matemática, vamos ao TDD com Flask, um caso muito mais tangível do que encontramos no nosso dia-a-dia.

3. Primeiros passos para a aplicação web

Criando um servidor web

Como nosso foco é começar uma aplicação web, podemos descartar os testes e o método fatorial que criamos no passo anterior. Ao invés disso, vamos escrever um teste simples, para ver se conseguimos fazer o Flask criar um servidor web.

Descarte tudo do tests.py substituindo o conteúdo do arquivo por essas linhas:

import unittest
from app import meu_web_app


class TestHome(unittest.TestCase):

    def test_get(self):
        app = meu_web_app.test_client()
        response = app.get('/')
        self.assertEqual(200, response.status_code)

if __name__ == '__main__':
    unittest.main()

Esse arquivo agora faz quatro coisas referentes a nossa aplicação web:

  1. Importa o objeto meu_web_app (que ainda não criamos) do nosso arquivo app.py;
  2. Cria uma instância da nossa aplicação web específica para nossos testes (é o método meu_web_app.test_client(), cujo retorno batizamos de app);
  3. Tenta acessar a “raíz” da nossa aplicação — ou seja, se essa aplicação web estivesse no servidor pythonclub.com.br estaríamos acessando http://pythonclub.com.br/.
  4. Verifica se, ao acessar esse endereço, ou seja, se ao fazer a requisição HTTP para essa URL, temos como resposta o código 200, que representa sucesso.

Os códigos de status de requisição HTTP mais comuns são o 200 (sucesso), 404 (página não encontrada) e 302 (redirecionamento) — mas a lista completa é muito maior que isso.

De qualquer forma não conseguiremos rodar esses testes. O interpretador do Python vai nos retornar um erro:

ImportError: cannot import name 'meu_web_app'

Então vamos criar o objeto meu_web_app lá no app.py. Descartamos tudo que tínhamos lá substituindo o contéudo do arquivo por essas linhas:

from flask import Flask

meu_web_app = Flask()

Apenas estamos importando a classe principal do Flask, e criando uma instância dela. Em outras palavras, estamos começando a utilizar o framework.

E agora o erro muda:

Traceback (most recent call last):
  File "tests.py", line 2, in <module>
    from app import meu_web_app
  File "/Users/cuducos/Desktop/flask/app.py", line 3, in <module>
    meu_web_app = Flask()
TypeError: __init__() missing 1 required positional argument: 'import_name'

Importamos nosso meu_web_app, mas quando instanciamos o Flask temos um problema. Qual problema? O erro nos diz: quando tentamos chamar Flask() na linha 3 do app.py está faltando um argumento posicional obrigatório (missing 1 required positional argument). Estamos chamando Flask() sem nenhum argumento. O erro ainda nos diz que o que falta é um nome (import_name). Vamos batizar nossa instância com um nome:

meu_web_app = Flask(`meu_web_app`)

E agora temos uma nova mensagem de erro, ou seja, progresso!

Eu amo testes que falham! A melhor coisa é uma notificação em vermelho me dizendo que os testes estão falhando. Isso significa que eu tenho testes e que eles estão funcionando!

Bruno Rocha

F
======================================================================
FAIL: test_get (__main__.TestHome)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 10, in test_get
    self.assertEqual(200, response.status_code)
AssertionError: 200 != 404

----------------------------------------------------------------------
Ran 1 test in 0.015s

FAILED (failures=1)

Temos uma aplicação web rodando, mas quando tentamos acessar a raíz dela, ela nos diz que a página não está definida, não foi encontrada (é o que nos diz o código 404).

Criando nossa primeira página

O Flask facilita muito a criação de aplicações web. De forma simplificada a qualquer método Python pode ser atribuída uma URL. Isso é feito com um decorador:

@app.route('/')
def pagina_inicial():
    return ''

Adicionando essas linhas no app.py, os testes passam:

.
----------------------------------------------------------------------
Ran 1 test in 0.013s

OK

Se a curiosidade for grande, esse artigo (em inglês) explica direitinho como o Flask.route(rule, **options) funciona: Things which aren't magic - Flask and @app.route.

Para garantir que tudo está certinho mesmo, podemos adicionar mais um teste. Queremos que a resposta do servidor seja um HTML:

def test_content_type(self):
    app = meu_web_app.test_client()
    response = app.get('/')
    self.assertIn('text/html', response.content_type)

Rodando os testes, veremos que agora temos dois testes. E ambos passam!

Eliminando repetições

Repararam que duas linhas se repetem nos métodos test_get() e test_content_type()?

app = meu_web_app.test_client()
response = app.get('/')

Podemos usar um método especial da classe unittest.TestCase para reaproveitar essas linhas. O método TestCase.setUp() é executado ao iniciar cada teste, e através do self podemos acessar objetos de um método a partir de outro método:

class TestHome(unittest.TestCase):

    def setUp(self):
        app = meu_web_app.test_client()
        self.response = app.get('/')

    def test_get(self):
        self.assertEqual(200, self.response.status_code)

    def test_content_type(self):
        self.assertIn('text/html', self.response.content_type)

Não vamos precisar nesse exemplo, mas o método TestCase.tearDown() é executado ao fim de cada teste (e não no início, como a setUp()). Ou seja, se precisar repetir algum comando sempre após cada teste, a unittest também faz isso para você.

4. Preenchendo a página

Conteúdo como resposta

Temos um servidor web funcionando, mas não vemos nada na nossa aplicação web. Podemos verificar isso em três passos rápidos:

Primeiro adicionamos essas linhas ao app.py para que, quando executarmos o app.py (mas não quando ele for importado no tests.py), a aplicação web seja iniciada:

if __name__ == "__main__":
    meu_web_app.run()

Depois executamos o arquivo:

$ python app.py

Assim vemos no terminal essa mensagem:

 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Se acessarmos essa URL no nosso navegador, podemos ver a aplicação rodando: http://127.0.0.1:5000/.

E veremos que realmente não há nada, é uma página em branco.

Vamos mudar isso! Vamos construir o que seria uma página individual, mostrando quem a gente é. Na minha vou querer que esteja escrito (ao menos), meu nome. Então vamos escrever um teste para isso:

def test_content(self):
    self.assertIn('Eduardo Cuducos', self.response.data)

Feito isso, teremos uma nova mensagem de erro nos testes:

TypeError: a bytes-like object is required, not 'str'

Essa mensagem nos diz que estamos comparando uma string com um objeto que é de outro tipo, que é representado por bytes. Não é isso que queremos. Como explicitamente passamos para o teste uma string com nosso nome, podemos assumir que é o self.response.data que vem codificado em bytes. Vamos decodificá-lo para string.

Bytes precisam ser decodificados para string (método decode). Strings precisam ser codificados para bytes para então mandarmos o conteúdo para o disco, para a rede (método encode).

Henrique Bastos

def test_content(self):
    self.assertIn('Eduardo Cuducos', self.response.data.decode('utf-8'))

Assim temos uma nova mensagem de erro:

AssertionError: 'Eduardo Cuducos' not found in "b''"

Nossa página está vazia, logo o teste não consegue encontrar meu nome na página. Vamos resolver isso lá no app.py:

@meu_web_app.route('/')
def pagina_inicial():
    return 'Eduardo Cuducos'

Agora temos os testes passando, e podemos verificar isso vendo que temos o nome na tela do navegador.

...
----------------------------------------------------------------------
Ran 3 tests in 0.015s

OK

Apresentando o conteúdo com HTML

O Python e o Flask cuidam principalmente do back-end da apliacação web — o que ocorre “por trás dos panos” no lado do servidor.

Mas temos também o front-end, que é o que o usuário vê, a interface com a qual o usuário interage. Normalmente o front-end é papel de outras linguagens, como o HTML, o CSS e o JavaScript.

Vamos começar com um HTML básico, criando a pasta templates e dentro dela o arquivo home.html:

<!DOCTYPE HTML>
<html>
  <head>
    <title>Eduardo Cuducos</title>
  </head>
  <body>
    <h1>Eduardo Cuducos</h1>
    <p>Sociólogo, geek, cozinheiro e fã de esportes.</p>
  </body>
</html>

Se a gente abrir essa página no navegador já podemos ver que ela é um pouco menos do que o que a gente tinha antes. Então vamos alterar nosso test_content() para garantir que ao invés de termos somente a string com nosso nome na aplicação, tempos esse template renderizado:

def test_content(self):
    response_str = self.response.data.decode('utf-8')
    self.assertIn('<title>Eduardo Cuducos</title>', str(response_str))
    self.assertIn('<h1>Eduardo Cuducos</h1>', str(response_str))
    self.assertIn('<p>Sociólogo, ', str(response_str))

Assim vemos nossos testes falharem:

F..
======================================================================
FAIL: test_content (__main__.TestHome)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 18, in test_content
    self.assertIn('<title>Eduardo Cuducos</title>', str(self.response.data))
AssertionError: '<title>Eduardo Cuducos</title>' not found in "b'Eduardo Cuducos'"

----------------------------------------------------------------------
Ran 3 tests in 0.017s

FAILED (failures=1)

Criamos um HTML, mas ainda não estamos pedindo para o Flask utilizá-lo. Temos nossa home.html dentro da pasta templates pois é justamente lá que o Flask vai buscar templates. Sabendo disso, podemos fazer nosso método index() retornar não a string, mas o template:

from flask import Flask, render_template



@meu_web_app.route('/')
def pagina_inicial():
    return render_template('home.html')

Assim voltamos a ter testes passando — e a página fica um pouco mais apresentável.

Formatando o conteúdo com CSS

Para não perder muito o foco do Python, TDD e Flask, vamos utilizar um framework CSS que se chama Bootstrap. Incluindo o CSS desse framework no nosso HTML, e utilizando algumas classes especificas dele, conseguimos dar uma cara nova para nossa aplicação.

Vamos escrever um teste para verificar se estamos mesmo carregando o Bootstrap:

def test_bootstrap_css(self):
    response_str = self.response.data.decode('utf-8')
    self.assertIn('bootstrap.min.css', response_str)

Os testes falham. Temos que linkar o CSS do Bootstrap em nosso HTML. Ao invés de baixar o Bootstrap, vamos utilizar o servidor CDN que eles mesmo recomendam. É só incluir essa linha no <head> do nosso HTML:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

Agora, com os testes passando, vamos utilizar as classes do Bootstrap para formatar melhor nossa página. Vamos retirar nosso <h1> e <p> e, ao invés disso, partir do componente Jumbotron fazendo algumas pequenas alterações:

<div class="container">
  <div class="jumbotron">
    <img src="https://avatars.githubusercontent.com/u/4732915?v=3&s=128" alt="Eduardo Cuducos" class="img-circle">
    <h1>Eduardo Cuducos</h1>
    <p>Sociólogo, geek, cozinheiro e fã de esportes.</p>
    <p><a class="btn btn-primary btn-lg" href="http://twitter.com/cuducos" role="button">Me siga no Twitter</a></p>
  </div>
</div>

Com essa página “incrementada” podemos ainda refinar nossos testes, garantindo que sempre temos a foto e o link:

def test_profile_image(self):
    response_str = self.response.data.decode('utf-8')
    self.assertIn('<img src="', response_str)
    self.assertIn('class="img-circle"', response_str)

def test_link(self):
    response_str = self.response.data.decode('utf-8')
    self.assertIn('href="http://twitter.com/cuducos"', response_str)
    self.assertIn('>Me siga no Twitter</a>', response_str)

Pronto, agora temos uma página formatada para mostrar para nossos colegas, com todos os testes passando:

......
----------------------------------------------------------------------
Ran 6 tests in 0.024s

OK

5. Conteúdos dinâmicos

Passando variáveis para o contexto do template

O problema da nossa página é que ela é estática. Vamos usar o Python e o Flask para que quando a gente acesse /cuducos a gente veja a minha página, com meus dados. Mas caso a gente acesse /z4r4tu5tr4, a gente veja o conteúdo referente ao outro Eduardo que palestrou comigo no Grupy.

Antes de mudar nossas URLS, vamos refatorar nossa aplicação e — importantíssimo! — os testes tem que continuar passando. A ideia é evitar que o conteúdo esteja “fixo” no template. Vamos fazer o conteúdo ser passado do método pagina_principal() para o template.

A ideia é extrair todo o conteúdo do nosso HTML criando um dicionário no app.py:

CUDUCOS = {'nome': 'Eduardo Cuducos',
           'descricao': 'Sociólogo, geek, cozinheiro e fã de esportes.',
           'url': 'http://twitter.com/cuducos',
           'nome_url': 'Twitter',
           'foto': 'https://avatars.githubusercontent.com/u/4732915?v=3&s=128'}

E, na sequência, usar esse dicionário para passar uma variável chamada perfil para o contexto do template:

@meu_web_app.route('/')
def pagina_inicial():
    return render_template('home.html', perfil=CUDUCOS)

Por fim, vamor utilizar, ao invés das minhas informações, a variável perfil no template:

<!DOCTYPE HTML>
<html>
  <head>
    <title>{{ perfil.nome }}</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  </head>
  <body>
    <div class="container">
      <div class="jumbotron">
        <img src="{{ perfil.foto }}" alt="{{ perfil.nome }}" class="img-circle">
        <h1>{{ perfil.nome }}</h1>
        <p>{{ perfil.descricao }}</p>
        <p><a class="btn btn-primary btn-lg" href="{{ perfil.url }}"
            role="button">Me siga no {{ perfil.nome_url }}</a></p>
      </div>
    </div>
  </body>
</html>

Feito isso, temos todas as informações disponíveis no nosso ambiente Python, e não mais no HTML. E os testes nos garantem que no final das contas, para o usuário, a página não mudou — ou seja, estamos mostrando as informações corretamente.

Criando conteúdo dinâmico

Vamos agora criar um outro dicionário para termos informações de outras pessoas. E vamos juntar todos os perfis em uma variável chamada PERFIS:

MENDES = {'nome': 'Eduardo Mendes',
          'descricao': 'Apaixonado por software livre e criador de lambdas.',
          'url': 'http://github.com/z4r4tu5tr4',
          'nome_url': 'GitHub',
          'foto': 'https://avatars.githubusercontent.com/u/6801122?v=3&s=128'}

PERFIS = {'cuducos': CUDUCOS,
          'z4r4tu5tr4': MENDES}

Agora, se utilizarmos nossa pagina_principal() com o primeiro perfil, nossos testes passam. Podemos passar o outro perfil e ver, no navegador, que já temos a nossa página com outras informações:

@meu_web_app.route('/')
def pagina_inicial():
    return render_template('home.html', perfil=PERFIS['z4r4tu5tr4'])

Mas se rodarmos os testes assim, veremos duas falhas:

.F..F.
======================================================================
FAIL: test_content (__main__.TestHome)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 19, in test_content
    self.assertIn('<title>Eduardo Cuducos</title>', str(response_str))
AssertionError: '<title>Eduardo Cuducos</title>' not found in '<!DOCTYPE HTML>\n<html>\n  <head>\n    <title>Eduardo Mendes</title>\n    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">\n  </head>\n  <body>\n    <div class="container">\n      <div class="jumbotron">\n        <img src="https://avatars.githubusercontent.com/u/6801122?v=3&amp;s=128" alt="Eduardo Mendes" class="img-circle">\n        <h1>Eduardo Mendes</h1>\n        <p>Apaixonado por software livre e criador de lambdas.</p>\n        <p><a class="btn btn-primary btn-lg" href="http://github.com/z4r4tu5tr4"\n            role="button">Me siga no GitHub</a></p>\n      </div>\n    </div>\n  </body>\n</html>'

======================================================================
FAIL: test_link (__main__.TestHome)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 34, in test_link
    self.assertIn('href="http://twitter.com/cuducos"', response_str)
AssertionError: 'href="http://twitter.com/cuducos"' not found in '<!DOCTYPE HTML>\n<html>\n  <head>\n    <title>Eduardo Mendes</title>\n    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">\n  </head>\n  <body>\n    <div class="container">\n      <div class="jumbotron">\n        <img src="https://avatars.githubusercontent.com/u/6801122?v=3&amp;s=128" alt="Eduardo Mendes" class="img-circle">\n        <h1>Eduardo Mendes</h1>\n        <p>Apaixonado por software livre e criador de lambdas.</p>\n        <p><a class="btn btn-primary btn-lg" href="http://github.com/z4r4tu5tr4"\n            role="button">Me siga no GitHub</a></p>\n      </div>\n    </div>\n  </body>\n</html>'

----------------------------------------------------------------------
Ran 6 tests in 0.024s

FAILED (failures=2)

Os testes nos dizem que bagunçamos as informações. Os testes de conteúdo não encontram mais Eduardo Cuducos na página, nem o link para http://twitter.com/cuducos.

Vamos arrumar isso fazendo um caso de teste para cada perfil. Vamos mudar também nosso esquema de URL. Ao invés de testar a raíz da aplicação, vamos testar se em /nome-do-usuário vemos as informações desse usuário.

Vamos renomear TestGet para TestCuducos e mudar a URL no setUp():

class TestCuducos(unittest.TestCase):

    def setUp(self):
        app = meu_web_app.test_client()
        self.response = app.get('/cuducos')

Agora podemos duplicar toda essa classe renomeando-a para TestZ4r4tu5tr4, substituindo as informações pertinentes:

class TestZ4r4tu5tr4(unittest.TestCase):

    def setUp(self):
        app = meu_web_app.test_client()
        self.response = app.get('/z4r4tu5tr4')

    def test_get(self):
        self.assertEqual(200, self.response.status_code)

    def test_content_type(self):
        self.assertIn('text/html', self.response.content_type)

    def test_content(self):
        response_str = self.response.data.decode('utf-8')
        self.assertIn('<title>Eduardo Mendes</title>', str(response_str))
        self.assertIn('<h1>Eduardo Mendes</h1>', str(response_str))
        self.assertIn('<p>Apaixonado por software livre', str(response_str))

    def test_bootstrap_css(self):
        response_str = self.response.data.decode('utf-8')
        self.assertIn('bootstrap.min.css', response_str)

    def test_profile_image(self):
        response_str = self.response.data.decode('utf-8')
        self.assertIn('<img src="', response_str)
        self.assertIn('class="img-circle"', response_str)

    def test_link(self):
        response_str = self.response.data.decode('utf-8')
        self.assertIn('href="http://github.com/z4r4tu5tr4"', response_str)
        self.assertIn('>Me siga no GitHub</a>', response_str)

Testes prontos… e falhando, claro. Não mudamos nosso esquema de URLs no Flask. Voltemos ao app.py.

Podemos começar com algo repetitivo, mas simples:

@meu_web_app.route('/cuducos')
def pagina_inicial_cuducos():
    perfil = PERFIS['cuducos']
    return render_template('home.html', perfil=perfil)


@meu_web_app.route('/z4r4tu5tr4')
def pagina_inicial_z4r4tu5tr4():
    perfil = PERFIS['z4r4tu5tr4']
    return render_template('home.html', perfil=perfil)

Como resultado, temos nossa aplicação com conteúdo dinâmico, com testes passando e funcionando!

Podemos melhorar um pouco mais. Essa repetição dos métodos pagina_inicial_cuducos() e pagina_inicial_z4r4tu5tr4() é facilmente evitada no Flask:

@meu_web_app.route('/<perfil>')
def pagina_inicial(perfil):
    perfil = PERFIS[perfil]
    return render_template('home.html', perfil=perfil)

Agora o Flask recebe uma variável perfil depois da / (e sabemos que é uma variável pois envolvemos o nome perfil entre os sinais de < e >). E utilizamos essa variável para escolher qual perfil passar para nosso tempate.

Considerações finais

Se chegou até aqui, vale a pena ressaltar que esse post tem apenas o objetivo de introduzir a ideia básica do TDD. Ou seja: ver como o hábito, o método de programar pouco a pouco (baby steps) e sempre começando com os testes te dão dois benefícios sensacionais: eles não só garantem que a aplicação funcionará como esperado, mas eles guiam o próprio processo de desenvolvimento. As mensagens de erro te dizer – muitas vezes literalmente — o qual é a próxima linha de código que você vai escrever.

E, se chegou até aqui, talvez você queira se aprofundar nos assuntos dos quais falamos. Além de inúmeros posts aqui do blog, ressalto mais algumas referências.

Leituras recomendadas para conhecer mais sobre Flask:

Leitura recomendada para conhecer mais sobre TDD:

  • Em inglês: Livro do Harry Percival
  • Em inglês: essa resposta no Stack Overflow sobre unit, integration, functional e acceptance test.

Quem aprendeu alguma coisa nova?

— Raymond Hettinger

por Eduardo Cuducos em 14 de March de 2016 às 14:59

Álvaro Jüsten

Reading Parquet Files in Python with rows

Many people in the data science field use the parquet format to store tabular data, as it's the default format used by Apache Spark -- an efficient data storage format for analytics. The problem is: the format is binary (you can't just open it with your preferred code editor) and there's no such a good Python library to read -- not until today!

I found a Python library called parquet-python on GitHub but it's hard to use, doesn't have one code example, was not available on PyPI and it looks like it's not maintained anymore. So I decided to implement a parquet plugin (read-only) for my library rows: it uses the parquet-python library under the hood (I needed to upload it to PyPI so you can install it easly) and exposes the data in a pretty simple, pythonic way.

Installation

I didn't realese the rows version with this plugin yet, so you need to grab the most recent rows version by running:

pip install -U git+https://github.com/turicas/rows.git@develop

And also the dependency:

pip install parquet

If the data is compressed using Google's snappy you'll also need the library headers and other Python dependency -- install everything by running:

apt-get install libsnappy-dev
pip install python-snappy

Then you can use rows.import_from_parquet(filename) in your programs! \o/

Python Example

A quick Python code example:

import rows

table = rows.import_from_parquet('myfile.parquet')
for row in table:
    print row  # access fields values with `rows.field_name`

Note that the current implementation is not optimized (for example, it'll put everything into memory) but at least you can extract desired data and then convert to a more friendly format easily.

Converting Parquet to Other Formats with rows' CLI

You can convert Parquet files to many tabular formats (like CSV) by using the rows's command-line interface, so you don't need to code.

Install the rows CLI by running:

pip install rows[cli]

Now convert the parquet file:

rows convert myfile.parquet myfile.csv  # yes, simple like this!

You can replace csv with any other supported format (the list is always growing!), such as: txt, html, xls, xlsx and sqlite.

If your file is small enough you can actually see it without needing to save the output to another file by using the print subcommand:

rows print myfile.parquet  # will extract, convert and print data as text

And you can actually query data as in SQL (this CLI is awesome!), for example:

rows query 'nation_key < 10' tests/data/nation.dict.parquet \
     --output=data.csv

By running this command the CLI will:

  • Import data from tests/data/nation.dict.parquet file into memory;
  • Export to SQLite (:memory:);
  • Run the query (nation_key < 10) and get the results;
  • Convert the results to a new rows.Table object;
  • Export the table to CSV format and save it into data.csv (the result format could be html, xls, xlsx or any other write-plugin supported by rows).

With this addition to rows I think the library and its command-line interface became one of the tools every data scientist should have installed. ;-)

por noreply@blogger.com (Álvaro Justen "Turicas") em 14 de March de 2016 às 04:48

March 13, 2016

Julio Cesar Eiras Melanda

Nova turma do curso de Django com CTNovatec

Olá!

Temos novidades! Abrimos mais uma turma de Django no CTNovatec!

Tem interesse em aprender o Framework Web Python usado pelo Pinterest e pelo Instagram? Então confira aqui:

http://ctnovatec.com.br/cursos/trilha-python/curso-de-django/

por admin em 13 de March de 2016 às 00:09

March 12, 2016

Thiago Avelino

I’m. very happy to see a non-technical person thinking that way.

I hope that after a year still continue without need of them, I know it’s not easy to stop using Google (Gmail), but it is possible and…

por Avelino em 12 de March de 2016 às 12:06

I’m. very happy to see a non-technical person thinking that way.

I hope that after a year still continue without need of them, I know it’s not easy to stop using Google (Gmail), but it is possible and…

por Avelino em 12 de March de 2016 às 12:05

I wrote an article talking about the learning curve for those who want to use vim.

Your article complements it, I’m going to link there https://medium.com/@avelino0/starting-to-use-vim-6c5fb3639f80#.o3ftpimm3

por Avelino em 12 de March de 2016 às 11:25

March 11, 2016

Aprenda Python

Technical tenets

Some technical tenets, in random order, to any human programmer: - Don't create technical debts. - Forge ahead a "MUP" (Minimal Useful Product). - Understand your users' needs. - Manage and kill technical debts, because you'll create them sooner or later. - Meet the deadlines. - Live with some technical debts. - Release often. - Keep it simple, but not simplistic. - Automate

por Vinicius Assef (noreply@blogger.com) em 11 de March de 2016 às 16:44

March 10, 2016

Magnun Leno

Hack ‘n’ Cast v0.18 - Hack ‘n’ Drops #002

Microsoft Windows 10, Mycroft e uma japa maker são os tópicos desse Hack 'n' Cast.

Baixe o episódio e leia o shownotes

por Magnun em 10 de March de 2016 às 03:38

March 07, 2016

Álvaro Jüsten

Searching dd-wrt Router Database with ddwrtdb

I really like the dd-wrt router operating system: I can install it on cheap routers (starting from 27 USD, as TP-Link WR741ND) to have a great Web configuration interface and performance (way better than general factory software).

I'm always looking for new router models to check if they're supported by dd-wrt and to compare prices/hardware specs (as I'm always buying new routers to help some friends with their Wi-Fi networks). The problem is: dd-wrt's website usability is not that good, specially the router database search. As I prefer to use my terminal instead of the Web browser, I've created a command-line tool to deal with it: it's called ddwrtdb and the code is available at my GitHub account!

It's also available on Python Package Index so you can install it using Python's pip by running:

pip install https://github.com/turicas/rows/archive/develop.zip
pip install ddwrtdb

That's it! Now run ddwrtdb --help to see the available commands (it's pretty intuitive). You can also check out the project' README for command examples.

This simple command-line tool (< 200 lines of Python code) was created using these awesome libraries:

  • click, to easily create a beautiful command-line interface;
  • lxml, to use XPath in order to parse HTML more easily;
  • requests, to make HTTP requests to dd-wrt's website;
  • rows, to automatically extract tables from HTML and to export data to any tabular format.

por noreply@blogger.com (Álvaro Justen "Turicas") em 07 de March de 2016 às 02:24

March 04, 2016

Bruno Cezar Rocha

Microservices with Python, RabbitMQ and Nameko

"Micro-services is the new black" - Splitting the project in to independently scalable services is the currently the best option to ensure the evolution of the code. In Python there is a Framework called "Nameko" which makes it very easy and powerful.

Micro services

The term "Microservice Architecture" has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services. - M. Fowler

I recommend reading the Fowler's posts to understand the theory behind it.

Ok I so what does it mean?

In brief a Micro Service Architecture exists when your system is divided in small (single context bound) responsibilities blocks, those blocks doesn't know each other, they only have a common point of communication, generally a message queue, and does know the communication protocol and interfaces.

Give me a real-life example

Consider you have an REST API, that API has an endpoint receiving some data and you need to perform some kind of computation with that data, instead of blocking the caller you can do it asynchronously, return an status "OK - Your request will be processed" to the caller and do it in a background task.

Also you want to send an email notification when the computation is finished without blocking the main computing process, so it is better to delegate the "email sending" to another service.

Scenario

enter image description here

Show me the code!

Lets create the system to understand it in practice.

Environment

We need an environment with:

  • A running RabbitMQ
  • Python VirtualEnv for services
  • Python VirtualEnv for API

Rabbit

The easiest way to have a RabbitMQ in development environment is running its official docker container, considering you have Docker installed run:

docker run -d --hostname my-rabbit --name some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management

Go to the browser and access http://localhost:15672 using credentials guest:guest if you can login to RabbitMQ dashboard it means you have it running locally for development.

enter image description here

The Service environment

Now lets create the Micro Services to consume our tasks. We'll have a service for computing and another for mail, follow the steps.

In a shell create the root project directory

$ mkdir myproject
$ cd myproject

Create and activate a virtualenv (you can also use virtualenv-wrapper)

$ virtualenv service_env
$ source service_env/bin/activate

Install nameko framework and yagmail

(service_env)$ pip install nameko
(service_env)$ pip install yagmail

The service code

Now having that virtualenv prepared (consider you can run service in a server and API in another) lets code the nameko RPC Services.

We are going to put both services in a single python module, but you can also split in separate modules and also run them in separate servers if needed.

In a file called service.py

import yagmail
from nameko.rpc import rpc, RpcProxy


class Mail(object):
    name = "mail"

    @rpc
    def send(self, to, subject, contents):
        yag = yagmail.SMTP('myname@gmail.com', 'mypassword')
        # read the above credentials from a safe place.
        # Tip: take a look at Dynaconf setting module
        yag.send(to, subject, contents)


class Compute(object):
    name = "compute"
    mail = RpcProxy('mail')    

    @rpc
    def compute(self, operation, value, other, email):
        operations = {'sum': lambda x, y: x + y,
                      'mul': lambda x, y: x * y,
                      'div': lambda x, y: x / y,
                      'sub': lambda x, y: x - y}
        try:
            result = operations[operation](value, other)
        except Exception as e:
            self.mail.send.async(email, "An error occurred", str(e))
            raise
        else:
            self.mail.send.async(
                email, 
                "Your operation is complete!", 
                "The result is: %s" % result
            )
            return result

Now with the above services definition we need to run it as a Nameko RPC service.

NOTE: We are going to run it in a console and leave it running, but in production it is recommended to put the service to run using supervisord or an alternative.

Run the service and let it running in a shell

(service_env)$ nameko run service --broker amqp://guest:guest@localhost
starting services: mail, compute
Connected to amqp://guest:**@127.0.0.1:5672//
Connected to amqp://guest:**@127.0.0.1:5672//

Testing it

Go to another shell (with the same virtenv) and test it using nameko shell

(service_env)$ nameko shell --broker amqp://guest:guest@localhost
Nameko Python 2.7.9 (default, Apr  2 2015, 15:33:21) 
[GCC 4.9.2] shell on linux2
Broker: amqp://guest:guest@localhost
>>>

You are now in the RPC client testing shell exposing the n.rpc object, play with it

>>> n.rpc.mail.send("name@email.com", "testing", "Just testing")

The above should sent an email and we can also call compute service to test it, note that it also spawns an async mail sending with result.

>>> n.rpc.compute.compute('sum', 30, 10, "name@email.com")
40
>>> n.rpc.compute.compute('sub', 30, 10, "name@email.com")
20
>>> n.rpc.compute.compute('mul', 30, 10, "name@email.com")
300
>>> n.rpc.compute.compute('div', 30, 10, "name@email.com")
3

Calling the micro-service through the API

In a different shell (or even a different server) prepare the API environment

Create and activate a virtualenv (you can also use virtualenv-wrapper)

$ virtualenv api_env
$ source api_env/bin/activate

Install Nameko, Flask and Flasgger

(api_env)$ pip install nameko
(api_env)$ pip install flask
(api_env)$ pip install flasgger

NOTE: In api you dont need the yagmail because it is service responsability

Lets say you have the following code in a file api.py

from flask import Flask, request
from flasgger import Swagger
from nameko.standalone.rpc import ClusterRpcProxy

app = Flask(__name__)
Swagger(app)
CONFIG = {'AMQP_URI': "amqp://guest:guest@localhost"}


@app.route('/compute', methods=['POST'])
def compute():
    """
    Micro Service Based Compute and Mail API
    This API is made with Flask, Flasgger and Nameko
    ---
    parameters:
      - name: body
        in: body
        required: true
        schema:
          id: data
          properties:
            operation:
              type: string
              enum:
                - sum
                - mul
                - sub
                - div
            email:
              type: string
            value:
              type: integer
            other:
              type: integer
    responses:
      200:
        description: Please wait the calculation, you'll receive an email with results
    """
    operation = request.json.get('operation')
    value = request.json.get('email')
    other = request.json.get('other')
    email = request.json.get('email')
    msg = "Please wait the calculation, you'll receive an email with results"
    subject = "API Notification"
    with ClusterRpcProxy(CONFIG) as rpc:
        # asynchronously spawning and email notification
        rpc.mail.send.async(email, subject, msg)
        # asynchronously spawning the compute task
        result = rpc.compute.compute.async(operation, value, other, email)
        return msg, 200

app.run(debug=True)

Put the above API to run in a different shell or server

(api_env) $ python api.py 
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

and then access the url http://localhost:5000/apidocs/index.html you will see the Flasgger UI and you can interact with the api and start producing tasks on queue to the service to consume.

[image]

NOTE: You can see the shell where service is running for logging, prints and error messages. You can also access the RabbitMQ dashboard to see if there is some message in process there.

There is a lot of more advanced things you can do with Nameko framework you can find more information on https://nameko.readthedocs.org/en/stable/

Let's Micro Serve!

por Bruno Rocha em 04 de March de 2016 às 20:53

February 28, 2016

Álvaro Jüsten

Detectando Encoding e Tipo de Arquivo com Python

Read this blog post in English.

O conteúdo desse artigo está disponível também em vídeo:

Alguns dos usuários da minha biblioteca rows me pediram funcionalidades de detecção de encoding e tipo de arquivo, então comecei a procurar alguma biblioteca Python simples e rápida que desse conta dessa tarefa. O problema: encontrei muitas bibliotecas e nenhuma delas me atraiu por alguns dos seguintes motivos:

  • Não tinha uma implementação pythônica;
  • Não estava disponível no Debian (se eu usasse o pacote rows no Debian iria quebrar);
  • Não estava sendo mantida atualmente;
  • Não funcionava corretamente.

Então percebi que não existe uma "solução de ouro" para esse problema em Python. Muitos pythonistas usam a biblioteca chardet, mas ela detecta muitas vezes com erros e é razoavelmente lenta (principalmente se você precisar fazer a detecção enquanto um usuário aguarda a resposta através de uma chamada de API via HTTP) -- veja mais detalhes sobre isso no vídeo.

But there should be one -- and preferably only one -- obvious way to do it.

Então, eu pensei: por que não usar o programa file, que já é bastante conhecido por hackers do mundo UNIX, é rápido e muito mais assertivo que todas as outras opções? Para minha surpresa não existia um bom binding de Python para a libmagic (algumas bibliotecas rodavam o comando file mas isso não era uma opção para mim pois dependia de mais um pacote do sistema e não era uma solução muito portável).

Depois de vasculhar o repositório de código do file eu encontrei um binding simples para Python, que na época não estava disponível no PyPI e não era tão pythônica como eu esperava.

A Solução

Como o código é software livre, eu criei uma issue no sistema de bugs do file para resolver o problema e o Christos Zoulas (atual mantenedor) me pediu um patch, que eu implementei, enviei e foi aceito. :-)

Fiquei muito feliz de poder colaborar com um software importante e que eu uso desde de meus primeiros passos no mundo GNU/Linux (2003? 2004?)! Durante minha busca eu descobri que o primeiro commit da versão livre do file é de 1987 (eu tinha menos de 4 meses de idade!) -- e o software ainda é mantido hoje.

Agora todos podemos usar binding oficial do file para Python: a nova biblioteca é chamada file-magic e pode ser instalada executando:

pip install file-magic

Ela disponibiliza várias funções e atributos, mas as mais importantes são bem simples e intuitivas: elas retornam uma namedtuple com os resultados da detecção. Vamos ao código!

>>> import magic

>>> # Você pode especificar diretamente um nome de arquivo
>>> filename_detected = magic.detect_from_filename('turicas.jpg')
>>> print(filename_detected)
FileMagic(mime_type='image/jpeg', encoding='binary',
          name='JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, progressive, precision 8, 842x842, frames 3')
>>> # E você pode acessar os atributos da `namedtuple`:
>>> print(filename_detected.mime_type)
image/jpeg

# E se você já tem o conteúdo do arquivo em memória:
>>> with open('data.html') as fobj:
...     data = fobj.read()
>>> content_detected = magic.detect_from_content(data)
>>> print(content_detected)
FileMagic(mime_type='text/html', encoding='utf-8',
          name='HTML document, UTF-8 Unicode text')
>>> print(content_detected.encoding)
utf-8

Algumas coisas ainda precisam ser melhoradas (como a documentação, rodar os testes em outras plataformas etc.), porém a biblioteca já pode ser instalada através do pip, é rápida e precisa. :-)

Espero que vocês tenham gostado! ;-)

por noreply@blogger.com (Álvaro Justen "Turicas") em 28 de February de 2016 às 18:14

Detecting File Type and Encoding In Python

Read this blog post in Brazilian Portuguese.

I was looking for a simple and fast Python library to implement proper file type detection and encoding detection into my rows library and found that there are many libraries available on Python Package Index which claim to do it. None of them attracted me because one of the following reasons:

  • Do not have a pythonic implementation,
  • Is not available on Debian to install as a package (it's important so people can install rows and its dependencies using pip or apt-get),
  • Is not maintained anymore, or
  • Have some missing feature.

None seemed to be the de-facto way to do it in Python (I think pythonistas do it in many ways). Many pythonistas use the chardet library but its results are wrong sometimes and it's pretty slow (specially if you need to detect during an API HTTP request, while the client is waiting).

But there should be one -- and preferably only one -- obvious way to do it.

So, I thought: why not use the file software, which is well known by all UNIX hackers, faster and most accurate than all the other solutions I know? To my surprise there was not such a good Python binding for file on PyPI (and calling it as a child process was not and option since it would add one more system-dependant package, not detectable during a pip install if missing and would also turn this solution less portable). Then, searching on its repository I found a simple Python wrapper, which was not available on PyPI at that time and was not that pythonic as I expected.

The Solution

Since it's free/libre software, I've created an issue on file bug tracker to solve the problem and Christos Zoulas (the current maintainer) asked for a patch, which I implemented, sent and was accepted. I'm pretty happy I can contribute to a software I've been using since my earlier times on GNU/Linux (2003? 2004?). During this process I found that the first commit on the free/libre file (which every GNU/Linux distribution and BSD flavor uses) implementation was done when I was less than 4 months old (!) -- and it's still maintained today.

Now you can use the official file Python binding: the new library is called file-magic and can be installed by running:

pip install file-magic

It provides some methods and attributes but the most important are pretty simple and intuitive to use: they return a namedtuple with the data you want! Let's take a tour through an example:

>>> import magic

>>> # You can pass the filename and it'll open the file for you:
>>> filename_detected = magic.detect_from_filename('turicas.jpg')
>>> print filename_detected
FileMagic(mime_type='image/jpeg', encoding='binary',
          name='JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, progressive, precision 8, 842x842, frames 3')
>>> # It's a `namedtuple` so you can access the attributes directly:
>>> print filename_detected.mime_type
image/jpeg

# If you have the file contents already, just use `detect_from_content`:
>>> with open('data.html') as fobj:
...     data = fobj.read()
>>> content_detected = magic.detect_from_content(data)
>>> print content_detected
FileMagic(mime_type='text/html', encoding='utf-8',
          name='HTML document, UTF-8 Unicode text')
>>> print content_detected.encoding
utf-8

There are still some things to be improved (like running tests in other platforms -- including Python 3) but it's pip-installable and usable now, so we can benefit from it. Feel free to contribute. :)

Hope you enjoy it!

por noreply@blogger.com (Álvaro Justen "Turicas") em 28 de February de 2016 às 18:08

February 26, 2016

PythonClub

Upload de arquivos no Django: entendendo os modos de leitura

Em uma conversa com a galera do Welcome to the Django acabei experimentando e aprendendo – na prática — sobre csv, strings, bytes, file object e a maneira como uploads funcionam. Registrei minha exploração e espero que mais gente possa encontrar uma ou outra coisa nova aqui!

O problema

Fui alterar um projeto feito com Django, atualizando do Python 2 para o Python 3, e me deparei com um pedaço de uma view que, como o Henrique Bastos falou, funcionava “por acaso” no Python 2:

def foobar(request):
        
    lines = csv.reader(request.FILES['file.csv'])
    for line in lines:
        

Essa view recebe um arquivo CSV (upload do usuáio) e só processa as linhas do arquivo, sem salvá-lo em disco. No Python 3, esse trecho da view passou a dar erro:

_csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)

O Henrique, além de falar que o código funcionava “por acaso”, me lembrou que o csv.reader(…) já recebe um arquivo aberto. Assim fui explorar a maneira que o Django estava me entregando os arquivos no HttpRequest (no caso da minha view, o que eu tinha em mãos no request.FILES['file.csv']).

Simulando o ambiente da view

Para explorar isso, eu precisava simular o ambiente da minha view. Comecei criando um arquivo simples, teste.txt:

Linha 1, foo
Linha 2, bar
Linha 3, acentuação

Depois fui ler a documentação do HttpRequest.FILES e descobri que os arquivos ali disponíveis são instâncias de UploadedFile.

Logo, se eu criar uma instância da classe UploadedFile, posso acessar um objeto do mesmo tipo que eu acessava na view pelo request.FILES['file.csv']. Para criar essa instância, preciso de um arquivo aberto, algo como open(file_path, modo). Para continuar a simulação, eu precisava saber de que forma o Django abre o arquivo do upload quando instancia ele no HttpRequest.FILES.

Eu desconfiava que não era em texto (r), que era em binário (rb). A documentação do curl, por exemplo, indicava que os arquivos eram enviados como binários. A documentação da Requests tem um aviso grande, em vermelho, desencorajando qualquer um usar outro modo que não o binário.

Lendo mais sobre o UploadedFile descobri que esse objeto tem um atributo file que, é uma referência ao file object nativo do Python que a classe UploadFile envolve. E esse atributo file, por sua vez, tem o atributo mode que me diz qual o modo foi utilizado na abertura do arquivo. Fui lá na minha view e dei um print(request.FILES['file.csv'].file.mode) e obtive rb como resposta.

Pronto! Finalmente eu tinha tudo para simular o ambiente da view no meu IPython:

import csv
from django.core.files.uploadedfile import UploadedFile
uploaded = UploadedFile(open('teste.txt', 'rb'), 'teste.txt')

Assim testei o trecho que dava problema…

for line in csv.reader(uploaded.file):
    print(line)

… e obtive o mesmo erro.

Solução

Como já tinha ficado claro, o arquivo estava aberto como binário. Isso dá erro na hora de usar o csv.reader(…), pois o csv.reader(…) espera um texto, string como argumento. Aqui nem precisei ler a documentação, só lembrei da mensagem de erro: did you open the file in text mode? – ou seja, você abriu o arquivo no modo texto?

Lendo a documentação do UploadedFile e do File do Django (já que a primeira herda da segunda), achei dois métodos úteis: o close() e o open(). Com eles fechei o arquivo que estava aberto no modo rb e (re)abri o mesmo arquivo como r:

uploaded.close()
uploaded.open('r')

Agora sim o arquivo está pronto para o csv.reader(…):

for line in csv.reader(uploaded.file):
    print(line)
['Linha 1', ' foo']
['Linha 2', ' bar']
['Linha 3', ' acentuação']

Enfim, esse métodos UploadedFile.close() e UploadedFile.open(mode=mode) podem ser muito úteis quando queremos fazer algo diferente de gravar os arquivos recebidos em disco.

Quem aprendeu alguma coisa nova?

— Raymond Hettinger

por Eduardo Cuducos em 26 de February de 2016 às 21:39

Aprenda Python

Como manter as dependências de pacotes garantindo as correções de bugs

**Versão TL;DR:** administre manualmente as dependências, fixe as versões dos pacotes de forma a garantir updates de correções, mas mantenha a compatibilidade. Trabalhando com Python é normal instalar pacotes que já fazem alguma tarefa que preciso e é comum projetos com mais de uma dezena de dependências externas. Com essa quantidade, surge a dificuldade de administrar todas as dependências

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2016 às 16:15

Visualizadores de arquivo reStructuredText

Os arquivos [reStructuredText](http://docutils.sourceforge.net/rst.html) (ou `rst`) são muito usados para escrever documentação técnica. Principalmente para aqueles projetos que têm a documentação hospedada no site [Read The Docs](http://readthedocs.org) ou que estão disponíveis no [pypi](http://pypi.python.org). O [Sphinx](http://www.sphinx-doc.org/en/stable/) é um projeto bem conhecido para

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2016 às 01:11

Visualizadores de arquivo Markdown

Os arquivos [Markdown](http://daringfireball.net/projects/markdown/) (ou `md`) tornaram-se muito conhecidos nos últimos anos devido à simplicidade. Eles ficaram mais populares quando o Github passou a recomendá-los como formato padrão de documentação, principalmente para arquivos README. Aproveitando essa popularidade, o Github fez acréscimos à sintaxe original e lançou o [Github Flavored

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2016 às 01:10

Não passe locals() para o template

Passar variáveis para templates é muito simples, mas existem detalhes aos quais você precisa estar atento para simplificar a manutenção do seu projeto. Nesse texto vou analisar o impacto de passar `locals()` para o template. Depois de ler esse artigo, continue com [Não passe objetos aninhados para o template](http://aprenda-python.blogspot.com/2016/02/

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2016 às 01:00

Não passe objetos aninhados para o template

Em geral as linguagens de template são limitadas e não permitem fazer tudo que a linguagem principal permite. Por exemplo, com a [linguagem de templates do Django](https://docs.djangoproject.com/en/1.9/ref/templates/language/) ou com [Jinja](http://jinja.pocoo.org) não é possível fazer cálculos, o que certamente Python permite. Essa abordagem traz benefícios e desvantagens. Um dos benefícios é

por Vinicius Assef (noreply@blogger.com) em 26 de February de 2016 às 00:59

February 25, 2016

Bruno Cezar Rocha

ESEngine - Elasticsearch Object Doctype Mapper for Python

What is ESEngine

esengine - The Elasticsearch Object Doctype Mapper

PyPI versions downloads Travis CI Coverage Status Code Health



ESEngine is an ODM (Object Doctype Mapper) heavily inspired by MongoEngine, developed with the idea that you have to "Know well your Elastic queries and then write them as Python objects"

You extend the esengine.Document class defining a bunch of fields and meta-attributes and you can use that model to instantiate documents and perform queries on ElasticSearch.

ESEngine is MIT licensed and is open source available at http://github.com/catholabs/esengine

The documentation is currently only a README full of examples in http://catholabs.github.io/esengine/ and also the DocString that can be read using Epydoc in http://catholabs.github.io/esengine/docs/

How it works?

Firstly you need an Elasticsearch Python Client, we recommend using the official one pip install elasticsearch, and then you can define your models using ESEngine objects.

# myproject/models.py
from elasticsearch import Elasticsearch
from esengine import Document, StringField, BooleanField

class Person(Document):
    # meta attributes
    _index = 'myproject'
    _doctype = 'person'

    # default client instance
    _es = Elasticsearch()  # optional, can be passed lazily or can be a callable 

    # field definitions
    name = StringField()
    active = BooleanField()

Person.init()

NOTE: The init() calling will initialize the index/doctype mappings and settings, this part can be omitted and then Elastic Search will try to create this by introspection when the first document is indexed.

With the model definition in a file like myproject/models.py we can now use the model class Person to Index(insert), edit, delete and of course search documents.

In a Python console:

>>> from myproject.models import Person

Indexing:

>>> user = Person(name=”Bruno”, active=True)
>>> user.save()
# or simply
>>> user =  Person.create(name=”Bruno”, active=True)

Updating

>>> user.active = False
>>> user.save()
# or simply
>>> user.update(active=False)

Filtering multiple documents

>>> users = Person.filter(active=True)
[ ResultSet generator… a list of active users ]

Bulk update

>>> users.update(active=False)

Performing raw queries (recommended)

>>> query = {“query”: {“match_all”: {}}, “sort”: “name”} 
>>> Person.search(query=query, size=10)

Querying using Payload helpers (better to create dynamic queries)

>>> from esengine import Payload, Query
>>> query = Query.match_all() 
>>> Payload(model=Person, query=query, sort=”name”).search(size=10)

Deleting documents

>>> user = Person.get(id=123)
>>> user.delete()
# or simply
>>> Person.delete_by_id(123)
# or in bulk
>>> users = Person.filter(active=False)
>>> Person.delete_all(users)
# ou simply
>>> Person.delete_by_query({“query”: …. })

You can find more examples in https://github.com/catholabs/esengine

Currently ESEngine is being used in 3 of Catholabs production projects and is reaching a nice level of performance and abstraction.

If you use ElasticSearch with Python or want to learn more about it you can follow the readme on github feel free to open issues or collaborating by Pull Requests Pull Requests :)

Let's Search!

por Bruno Rocha em 25 de February de 2016 às 13:16

February 23, 2016

Bruno Cezar Rocha

Flasgger - API playground with Flask and Swagger UI

What is Swagger?

Swagger is a simple yet powerful representation of your RESTful API. With the largest ecosystem of API tooling on the planet, thousands of developers are supporting Swagger in almost every modern programming language and deployment environment. With a Swagger-enabled API, you get interactive documentation, client SDK generation and discoverability.

What is Swagger UI?

Swagger UI is a dependency-free collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation and sandbox from a Swagger-compliant API. Because Swagger UI has no dependencies, you can host it in any server environment, or on your local machine. Head over to the online demo to see what it looks like for any publically accessible Swagger definition.

What is Flask? (duhhhh!!)

Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions. Why it is awesome? because it is simple yet powerful, talking is cheaper look at the code!

from flask import Flask, jsonify, request
app = Flask(__name__)

@app.route('my_awesome_api', methods=['POST'])
def my_awesome_endpoint():
    data = request.json
    return jsonify(data=data, meta={"status": "ok"})

app.run()

Run the above script and then you can start posting to the API

curl -XPOST http://localhost:5000/my_awesome_api -d '{"python": "is awesome"}'
{
    "data": {"python": "is awesome"},
    "meta": {"status": "ok"}
}

What is Flasgger?

Flasgger is a Flask extension to help the creation of Flask APIs with documentation and live playground powered by SwaggerUI. You can define your API structure using YAML files and Flasgger creates all the specifications for you and you can use the same schema to validate the data.

GITHUB REPO: https://github.com/rochacbruno/flasgger

Install it

pip install flasgger

Create your app

You can put API specs directly in docstrings

import random
from flask import Flask, jsonify, request
from flasgger import Swagger

app = Flask(__name__)
Swagger(app)

@app.route('/api/<string:language>/', methods=['GET'])
def index(language):
    """
    This is the language awesomeness API
    Call this api passing a language name and get back its features
    ---
    tags:
      - Awesomeness Language API
    parameters:
      - name: language
        in: path
        type: string
        required: true
        description: The language name
      - name: size
        in: query
        type: integer
        description: size of awesomeness
    responses:
      500:
        description: Error The language is not awesome!
      200:
        description: A language with its awesomeness
        schema:
          id: awesome
          properties:
            language:
              type: string
              description: The language name
              default: Lua
            features:
              type: array
              description: The awesomeness list
              items:
                type: string
              default: ["perfect", "simple", "lovely"]

    """

    language = language.lower().strip()
    features = [
        "awesome", "great", "dynamic", 
        "simple", "powerful", "amazing", 
        "perfect", "beauty", "lovely"
    ]
    size = int(request.args.get('size', 1))
    if language in ['php', 'vb', 'visualbasic', 'actionscript']:
        return "An error occurred, invalid language for awesomeness", 500
    return jsonify(
        language=language,
        features=random.sample(features, size)
    )


app.run(debug=True)

Try it!

Now run your app and access http://localhost:5000/apidocs/index.html and you will play with Swagger UI!

Screenshot Flasgger

NOTE: All the default urls can be changed in configuration.

It is also possible to use a separate file for specs

Create your api specification in a separated YML file

In a file index.yml put the specs definitions

    This is the language awesomeness API
    Call this api passing a language name and get back its features
    ---
    tags:
      - Awesomeness Language API
    parameters:
      - name: language
        in: path
        type: string
        required: true
        description: The language name
      - name: size
        in: query
        type: integer
        description: size of awesomeness
    responses:
      500:
        description: Error The language is not awesome!
      200:
        description: A language with its awesomeness
        schema:
          id: awesome
          properties:
            language:
              type: string
              description: The language name
              default: Lua
            features:
              type: array
              description: The awesomeness list
              items:
                type: string
              default: ["perfect", "simple", "lovely"]

and then change the code to read from it using swag_from decorator

import random
from flask import Flask, jsonify, request
from flasgger import Swagger
from flasgger.utils import swag_from

app = Flask(__name__)
Swagger(app)

@app.route('/api/<string:language>/', methods=['GET'])
@swag_from('index.yml')
def index(language):
    language = language.lower().strip()
    features = [
        "awesome", "great", "dynamic", 
        "simple", "powerful", "amazing", 
        "perfect", "beauty", "lovely"
    ]
    size = int(request.args.get('size', 1))
    if language in ['php', 'vb', 'visualbasic', 'actionscript']:
        return "An error occurred, invalid language for awesomeness", 500
    return jsonify(
        language=language,
        features=random.sample(features, size)
    )


app.run(debug=True)

validation

If you put the specs in a separate file it is also possible to use the same specs to validate the input

from flasgger.utils import swag_from, validate, ValidationError

@app.route('/api/<string:language>/', methods=['GET'])
@swag_from('index.yml')
def index(language):
    ...
    try:
        validate(data, 'awesome', 'index.yml', root=__file__)
    except ValidationError as e:
        return "Validation Error: %s" % e, 400
    ...

More information

You can find more information and some examples in the github repository

Contribute

Please share your thoughts about ir, open issues, give ideas and PullRequests are always welcome!

Let's Swag!

por Bruno Rocha em 23 de February de 2016 às 11:40

February 21, 2016

Thiago Avelino

Steal the scene, do get noticed (to be different)

I’m at a point in my life I’m studying a lot about entrepreneurship and how great businessmen made to your designs continue to evolve. For…

por Avelino em 21 de February de 2016 às 22:19

Deploy software in production without testing

You certainly have developed (or develop) software without test and when put into production test an unexpected surprise, see a video that…

por Avelino em 21 de February de 2016 às 05:27

February 19, 2016

PythonClub

Python Generators

Publicado originalmente em: http://andrealmar.com/2016/02/generators

I'm the Generator, firing whenever you quit Yeah whatever it is, you go out and it's on
Yeah can't you hear my motored heart You're the one that started it


Foo Fighters - Generator


Não...eu não vou escrever neste post sobre a ótima música do Foo Fighters, embora recomendo que você a escute =P. Estamos aqui para falar de uma função bastante legal na nossa linguagem de programação preferida: Os famosos Generators.

Em termo simples os Generators são funções que permitem a você declarar uma função que se comporta como um iterador, ou seja, que pode ser usado dentro de um loop for.

Simplificando mais ainda: Generators são uma forma simples de criarmos iteradores. Ele irá retornar um objeto (iterador) para que nós possamos iterar sobre este objeto (um valor de cada vez).

É muito simples criar uma função Generator, mas existem algumas peculiaridades. Por exemplo, nós usamos a declaração yield ao invés de return. Se a função contém ao menos uma declaração yield então ela se torna uma função Generator.

Um exemplo bem simples. Abra o seu interpretador Python e digite a função abaixo:

def generator():
    n = 1
    print("Essa  uma função Generator")
    yield n

    n += 1
    yield n

    n += 1
    yield n

Vamos agora, executar a função no interpretador Python:

>>> # Retorna um objeto mas não executa a função imediatamente.
>>> a = generator()

>>> # Podemos iterar sobre os items usando next().
>>> next(a)
Essa  uma função Generator
1
>>> # Assim que a função executa o yield, ela é pausada e o controle da execução é transferido para quem a chamou.

>>> # Variáveis locais e os seus estados são "lembradas" durante as sucessivas chamadas à função.
>>> next(a)
2
>>> next(a)
3

>>> # Quando a função termina, a exceção StopIteration é levantada automaticamente.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

Interessante notar que o valor da variável a no exemplo acima é lembrada durante cada chamada do método next(). Nós declaramos 3 yields, então o valor da variável a será lembrado por 3 vezes. Quando tentamos chamar next(a) pela 4a vez, veja o que acontece:

>>> # Quando a função termina, a exceção StopIteration é levantada automaticamente.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

Uma exceção StopIteration é lançada, alertando que a iteração acabou. Ou seja, as variáveis locais NÃO são destruídas quando usamos o yield. O objeto Generator só pode ser iterado uma única vez. Se quisermos restartar o processo nós precisaremos criar um outro objeto Generator, por exemplo b = generator().

Também podemos utilizar Generators dentro de um laço for diretamente. Isso porque o laço for também utiliza a função next() para iterar e automaticamente encerra a iteração quando a exceção StopIteration é lançada.

>>> for item in generator():
...     print(item)
...
Essa é uma função Generator
1
2
3
>>

Generators Expressions

As Generators Expressions facilitam à criação de Generators. Assim como uma função lambda cria uma função anônima, uma Generator Expression cria uma função Generator anônima. A sintaxe é bem parecida com as famosas List Comprehensions com o pequeno detalhe de que os colchetes [ ] são subsituídos pelos parênteses ().

list_comprehension = [1,2,3,4,5,6,7,8]
generator_expression = (1,2,3,4,5,6,7,8)
>>> x = [1, 2, 3, 4, 5, 6, 6, 8]
>>> x
[1, 2, 3, 4, 5, 6, 6, 8]
>>> generator_expression = (i for i in x)
>>> generator_expression
<generator object <genexpr> at 0x101812af0>
>>> list_comprehension = [i for i in x]
>>> list_comprehension
[1, 2, 3, 4, 5, 6, 6, 8]
>>> 

Note no exemplo acima que a List Comprehension nos retorna a lista em si mas a Generator Expression nos retorna o objeto gerado: <generator object <genexpr> at 0x101812af0>.

A outra vantagem é que enquanto a List Comprehension gera a lista inteira, a Generator Expression gera um item de cada vez. Isso também é chamado de lazy ou on demand generation of values. E por consequência de ser lazy (preguiçosa), a Generator Expression consome bem menos memória sendo mais eficiente do que uma List Comprehension.

Espero que tenham gostado dessa explicação a respeito dos Generators e quaisquer dúvidas ou sugestões deixem seus comentários abaixo.

{}'s

por Andre Almar em 19 de February de 2016 às 18:15

Paralelismo em Python usando concurrent.futures

Esse post tem por objetivo abordar o uso da bliblioteca concurrent.futures para realizar operações paralelas em Python. Dito isto, gostaria de contextualizar de forma simples paralelismo e concorrência:

  • Concorrência: é quando um computador que possui apenas um core parece estar realizando duas ou mais operações ao mesmo tempo, quando na verdade está alternando a execução destas operações de forma tão rápida que temos a ilusão de que tudo é executado simultaneamente. e
  • Paralelismo: é quando um computador que possui dois ou mais cores executa operações realmente de forma paralela, utilizando para isso os cores disponíveis, ou seja, se um determinado computador tem 2 cores, posso ter duas operações sendo executadas paralelamente cada uma em um core diferente.

Infelizmente o GIL (Global Interpreter Lock do Python) é restritivo quanto ao uso de threads paralelas em Python, porém o módulo concurrent.futures permite que possamos utilizar múltiplos cores. Para isso, este módulo "engana" o GIL criando novos interpretadores como subprocessos do interpretador principal. Desta maneira, cada subprocesso tem seu próprio GIL e, por fim, cada subprocesso tem um ligação com o processo principal, de forma que recebem instruções para realizar operações e retornar resultados.

Agora que já vimos um pouco de teoria vamos colocar em prática o uso do concurrent.futures. Vamos supor que tenhamos um lista de preços e que queremos aumentar em 10% o valor de cada item.

Vamos então criar uma função que gere uma lista de preços:

def generate_list():
    result = []
    for i in range(0, 20):
        result.append(pow(i, 2) * 42)

    return result

Agora vamos criar uma função que calcule o preço acrescido de 10%.

def increase_price_by_10_percent(price):
    price += price / 10 * 100
    return price

Dando continuidade, definiremos mais três funções.

def increase_price_serial(price_list, increase_function):
    start = datetime.now()
    result = list(map(increase_function, price_list))
    end = datetime.now()
    print("Took {}s to increase the values".format((end - start).total_seconds()))

def increase_price_with_threads(price_list, increase_function):
    start = datetime.now()
    pool = ThreadPoolExecutor(max_workers=2)
    results = list(pool.map(increase_function, price_list))
    end = datetime.now()
    print("Took {}s to increase the prices with python Threads".format((end - start).total_seconds()))

def increase_price_with_subprocess(price_list, increase_function):
    start = datetime.now()
    pool = ProcessPoolExecutor(max_workers=2)
    results = list(pool.map(increase_function, price_list))
    end = datetime.now()
    print("Took {} to increase the prices with sub proccess".format((end - start).total_seconds()))

Note que as funções increase_price_serial, increase_price_with_threads e increase_price_with_subprocess são bem semelhantes, todas tem dois parâmetros:

  • o price_list, que é a lista de preços onde iremos fazer as operações ;
  • e o increase_function que é função que realizará as operações de acréscimo em cada item da lista.

A diferença entre estas funções está na forma em que as operações de acréscimo serão executadas conforme explicarei a seguir:

  • increase_price_serial: aqui a função passada pelo parâmetro increase_function será executada para cada item da price_list de forma sequencial.
  • increase_price_with_threads: aqui já começamos a fazer uso da classe ThreadPoolExecutor, que pertencente a lib concurrent.futures, e que vai nos permitir executar a increase_function de forma concorrente. Note que ao instanciar ThreadPoolExecutor estamos passando o parâmetro max_workers=2, isto está indicando o numero máximo de threads que será usado para executar as operações.
  • increase_price_with_subprocess: nesta função estamos fazendo uso da classe ProcessPoolExecutor que tem a funionalidade bastante semelhante à classe ThreadPoolExecutor exceto pelo fato de que esta classe permite que a função increase_function() seja executada realmente de forma paralela. Essa "mágica" é conseguida da seguinte forma:
    1. Cada item da lista de preços é serializado através do pickle;
    2. Os dados serializados são copiados do processo principal para os processos filhos por meio de um socket local;
    3. Aqui o pickle entra em cena novamente para deserializar os dados para os subprocessos;
    4. Os subprocessos importam o módulo Python que contém a função que será utilizada; no nosso caso, será importado o módulo onde increase_function está localizada;
    5. As funções são executadas de forma paralela em cada subprocesso;
    6. O resultado destas funções é serializado e copiado de volta para o processo principal via socket;
    7. Os resultados são desserializados e mesclados em uma lista para que possam ser retornados;

Nota-se que a classe ProcessPoolExecutor faz muitos "malabarismos" para que o paralelismo seja realmente possível.

Os resultados

Na minha máquina, que tem mais de um core, executei o seguinte código:

    prices = generate_list()
    increase_price_serial(prices, increase_price_by_10_percent)
    increase_price_with_threads(prices, increase_price_by_10_percent)
    increase_price_with_subprocess(prices, increase_price_by_10_percent)

Trazendo os seguintes resultados:

Função # Execução # Tempo gasto
increase_price_serial # Sequencial # 2.2e-05 secs
increase_price_with_threads # Concorrente # 0.001646 secs
increase_price_with_subprocess # Paralela # 0.016269 secs

Veja que increase_price_with_subproces, mesmo sendo executada paralelamente, levou mais tempo que increase_price_serial. Isso ocorreu pois a função increase_price_by_10_percent, que é utilizada para fazer operações nos itens da lista, é uma função que não exige muito trabalho do processador. Desta forma, o ProcessPoolExecutor leva mais tempo fazendo o processo de paralelização propriamente dito do que realmente executando as operações de cálculo.

Vamos criar neste momento uma função que realize operações mais complexas:

def increase_price_crazy(price):
    price += price / 10 * 100
    new_prices = []
    for i in range(0, 200000):
        new_prices.append(price + pow(price, 2))
    new_prices = map(sqrt, new_prices)
    new_prices = map(sqrt, new_prices)

    return max(price, min(new_prices))

Nota: Esta função foi criada apenas para efeitos didáticos.

Vamos agora ulilizar esta função no lugar da função increase_price_by_10_percent:

    increase_price_serial(prices, increase_price_crazy)
    increase_price_with_threads(prices, increase_price_crazy)
    increase_price_with_subprocess(prices, increase_price_crazy)

Obtendo o reultado abaixo:

Função # Execução # Tempo gasto
increase_price_serial # Sequencial # 4.10181 secs
increase_price_with_threads # Concorrente # 4.566346 secs
increase_price_with_subprocess # Paralela # 2.082025 secs

Nota: os valores de tempo gasto vão variar de acordo com o hardware disponível.

Veja que agora função increase_price_with_subprocess foi a mais rápida. Isto se deve o fato de que a nossa nova função ne cálculo increase_price_crazy demanda muito mais processamento , assim, o overhead para que se paralelize as operações tem um custo inferior ao custo de processamento das operações de cálculo.

Conclusão

Podemos concluir que é possível executar operações paralelas em python utilizando ProcessPoolExecutor, porém paralelizar nem sempre vai garantir que determinada operação vai ser mais performática. Temos sempre que avaliar a situação que temos em mãos.

Espero que este post tenha contribuído de alguma forma com conhecimento de vocês, sugestões e criticas serão bem vindas, obrigado!.

Disclaimer: Existem varios conceitos como, locks, deadlocks, futures, data races e etc. que não foram abordados aqui para que o post não ficasse muito longo e complexo. A Versão do python utilizada foi a 3.5, a lib concurrent.futures está dispónivel desde a versão 3.2 do Python, no entanto, exite um backport para a versão 2.7 que é facilmente instalável via 'pip install futures'.

O código completo pode ser encontrado aqui.

por José Cordeiro de Oliveira Junior em 19 de February de 2016 às 11:00

February 16, 2016

Thiago Avelino

February 15, 2016

Thiago Avelino

Addiction in writer, no more Medium (or not)

I’m a few days thinking of putting my personal blog in the Medium, today use blog generator via markdown (using github pages). As I’m…

por Avelino em 15 de February de 2016 às 17:31

February 14, 2016

Bruno Cezar Rocha

Dynaconf - Let your settings to be Dynamic

Dynaconf

dynaconf - The dynamic configurator for your Python Project

MIT License PyPI downloads Travis CI Coverage Status Code Health

dynaconf is an OSM (Object Settings Mapper) it can read settings variables from a set of different data stores such as python settings files, environment variables, redis, memcached, ini files, json files, yaml files and you can customize dynaconf loaders to read from wherever you want. (maybe you really want to read from xml files ughh?)

GITHUB REPO: https://github.com/rochacbruno/dynaconf



What is Dynaconf?

Dynaconf is a common point of access to settings variables, you import only one object in your project and from that object you can access settings variables from Python settings file, from environment variables, from parsed yaml, ini, json or xml files, from datastores as Redis and MongoDB or from wherever your need if you write a simple dynaconf loader.

How it works

Install it

pip install dynaconf

Use it

from dynaconf import settings
print settings.SOME_VARIABLE
or
print settings.get('SOME_VARIABLE')

By default Dynaconf will try to use a file called settings.py on the root of your project, if you place that file there all upper case variables will be read

You can also replace the file exporting an environment variable pointing to the module or location for the settings file.

# using module name
export DYNACONF_SETTINGS=myproject.production_settings
# or using location path
export DYNACONF_SETTINGS=/etc/myprogram/settings.py

Doing that when you use from dynaconf import settings the variables will be read from that file.

So how it is Dynamic?

Now think you have your program done and you want to deploy to a certain infrastructure for testing or maybe different deployment, you don't need to rewrite the settings file. Just export some variables to your environment.

export DYNACONF_MYSQL_HOST=myserver.com

Now in your project you can do:

from dynaconf import settings
print settings.MYSQL_HOST
myserver.com

The default prefix for exported envvars is by default DYNACONF_ but you also can change it if needed.

But what if I have some typed values to export?

You can also define type casting when exporting and those types will be used to parse the values.

export DYNACONF_NUMBER='@int 123'
export DYNACONF_FLOAT='@float 12.2'
export DYNACONF_FLAG='@bool yes'
export DYNACONF_FLAG2='@bool disabled'
export DYNACONF_LIST='@json [1, 2, 3, 4]'
export DYNACONF_DICT='@json {"name": "Bruno"}'

Now you can read all those values from your project and it will be loaded with correct type casting.

from dynaconf import settings

type(settings.NUMBER)
int

type(settings.FLOAT)
float

type(settings.FLAG)
bool

print settings.FLAG2 == False
True

print settings.LIST[1]
2

print settings.DICT['name']
Bruno

Nice! But I don't want to use envvars because I use autoscaling and I want my machines to share a settings environment how to do it?

Redis

Go to your settings file (default settings.py) and put

# connection
REDIS_FOR_DYNACONF = {
    'host': 'localhost',
    'port': 6379,
    'db': 0
}

# and loader
LOADERS_FOR_DYNACONF = [
    'dynaconf.loaders.env_loader',
    'dynaconf.loaders.redis_loader'
]

Now you can store settings variables directly in Redis using a hash named by default DYNACONF_DYNACONF

If you don't want want to write directly you can use the Redis writer helper in a python REPL. (ipython as example)

from dynaconf.utils import redis_writer
from dynaconf import settings

redis_writer.write(settings, name='Bruno', mysql_host='localhost', MYSQL_PORT=1234)

And the above will be store in Redis as a hash int the form.

DYNACONF_DYNACONF:
    NAME='Bruno'
    MYSQL_HOST='localhost'
    PORT='@int 1234'

And of course you can now read those variables in the project, all the casting wildcards also works on Redis but if you want to skip type casting, write as string intead of PORT=1234 use PORT='1234' as redis stores everything as string anyway.

There is more

Dynaconf has support for using different namespaces in the same project, you can also write your own loaders, you can find more information on the repository https://github.com/rochacbruno/dynaconf

Contribute

All contributions are very welcome!!

Acknowledgements

Dynaconf was inspired by Flask app config and also by Django settings module.

por Bruno Rocha em 14 de February de 2016 às 13:52

February 13, 2016

Eric Hideki

Thiago Avelino

February 11, 2016

Álvaro Jüsten

Fatura do NuBank em CSV

O Que é NuBank?

Para quem não conhece, NuBank é um cartão de crédito que não te cobra anuidade e tem um ótimo atendimento, além de um aplicativo para celular bastante fácil de usar (é sua única interface com eles). Em resumo: eles estão fazendo o que em geral os bancos não fazem e estão lucrando com isso! Já faz alguns meses que estou usando e recomendo! Se quiser saber mais detalhes acesse a página deles.

Problemas

O NuBank, apesar de muito bom, para mim poderia melhorar em alguns detalhes:

  • Ainda não dá para transferir as milhas do cartão para compra de passagens aéreas;
  • Ainda não consigo colocar o pagamento da fatura como débito automático no Banco do Brasil (parece que só está disponível para Santander);
  • É chato ter que usar o PDF da fatura para conferir meus gastos.

Quando estou no Brasil tento usar o NuBank para todas as compras, pois isso facilita muito o controle dos meus gastos - caso não concorde que gastar no cartão é melhor para ter o controle dos gastos então leia o adendo (quando estou fora evito usar o cartão de crédito pois o IOF para compras internacionais é de 6,38%). Porém, como a fatura vem por email em PDF e não dá pra exportar para outros formatos pelo aplicativo, torna-se trabalhoso (e chato) demais o processo de jogar os gastos para minha planilha financeira pessoal e conferí-los. Como adoro programar e programar é, em resumo, automatizar coisas, resolvi criar um software para fazer a conversão da fatura do NuBank PDF para CSV (que antes eu fazia manualmente). :D

Convertendo a Fatura

O código do software está todo na minha conta no GitHub, chama-se nubank-to-csv. Ele foi escrito em Python usando a minha biblioteca rows (para facilitar a extração para CSV e qualquer outro formato tabular) e a biblioteca lxml para extrair os dados do HTML que é gerado pelo pdftohtml. Fique à vontade para contribuir com o script caso você manje dos paranauê (fiz uma lista de sugestões de contribuição).

O fluxo então é esse:

Fatura em PDF >[pdftohtml]> Fatura em HTML >[nubank-to-csv]> Fatura em CSV

Depois de instalar o nubank-to-csv e suas dependências basta rodar dois comandos:

pdftohtml fatura.pdf
python nubank.py faturas.html fatura-lindona.csv

O arquivo na segunda linha é faturas.html (com "s") mesmo -- esse arquivo é gerado pelo pdftohtml (junto com outros) depois da conversão. Você pode deletar os arquivos gerados pelo pdftohtml depois disso. Esse comando não segue bem a filosofia do UNIX e é bem inflexível (não dá pra especificar o nome do arquivo de saída, por exemplo). :-/

Uma coisa legal do script é que ele já junta as entradas relacionadas a IOF com o gasto que gerou o IOF, facilitando bastante meu controle! :D

Convertendo o CSV para Outro Formato

Se você tem a command-line interface da biblioteca rows instalada (pip install rows ou apt-get install rows) você também pode converter o CSV para diversos outros formatos, como XLS, XLSX, JSON, SQLite, HTML e TXT. Para converter, basta rodar:

rows convert fatura-lindona.csv fatura.xls

Caso queira outro formato em vez de XLS, basta trocar "xls" ali pela extensão desejada que o software é esperto o suficiente para identificar. :)

Adendo: Controle das Contas

Muita gente prefere evitar o uso do cartão de crédito para ter mais controle das contas (já vi muitos conselhos vindos de profissionais do ramo financeiro sobre evitar o uso do cartão). Eu prefiro usar o cartão sempre que possível (quando estou no Brasil) pois quando gasto em dinheiro em geral esqueço facilmente com o que gastei (e eu gosto de saber com o que gastei meu dinheiro - essa é a primeira ação para conseguir manter uma vida financeira saudável).

Muita gente se assusta quando eu digo isso porque a maior parte das pessoas que usa o cartão de crédito tem "uma surpresa" quando chega a fatura. Eu nunca tenho surpresas pois:

  • Na planilha onde controlo meus ganhos e gastos já tenho estimativas de entrada e saída para os próximos meses;
  • Duas vezes por semana vejo pelo aplicativo do NuBank o que gastei nos últimos dias e lanço na planilha do próximo mês (que é quando terei que pagar a fatura); e
  • Quando a fatura chega eu a converto para CSV, abro no LibreOffice e confiro se está de acordo com o que lancei na minha planilha financeira anteriormente.

Com esses simples passos eu sei exatamente o valor da fatura que virá e tenho como me conter caso esteja gastando demais!

Curtiu? Que tal compartilhar com seus amigos? ;)

Dúvidas e sugestões? Comente! :)

por noreply@blogger.com (Álvaro Justen "Turicas") em 11 de February de 2016 às 14:12

PythonClub

Salvando gráfico de contribuições do Github com Python e Selenium

Como alguns sabem, sou apaixonado por Python. Atualmente, minha linguagem favorita por conta de sua simplicade e poder (além de ficar LINDJA toda indentada, hahahaha).

Uma das coisas mais legais da linguagem é a enorme quantidade de bibliotecas disponíveis. Cada dia que abro um grupo de discussão acabo conhecendo alguma funcionalidade interessante. Se você faz parte de algum desses grupos, provavelmente já viu o post do Alex Recker "Using Selenium to Buy a Bus Pass", em que ele mostra como automatizou a compra de passagens de ônibus com Selenium e Python.

Eu já havia ouvido falar do Selenium, mas nunca tinha experimentado na prática e o post do Alex foi o empurrão que faltava.

Obviamente, meu projetinho é bem mais simples, mas foi algo divertido de se fazer como forma de aprendizado. Batizei-o de GHSS(Github Screenshot). Como o próprio nome sugere, ele entra no seu perfil do Github e tira um screenshot do gráfico de contribuições, salvando com a data atual.

Abaixo, irei mostrar como fazer. Visto que há muita gente que usa Python sem ser programador por profissão, tentarei explicar de forma mais simples possível. O código completo pode ser encontrado no meu Github.


Neste código, utilizaremos o Python2.

Primeiramente, temos que importar todas as bibliotecas necessárias.

Na linha 1, importamos o "OS", que será utilizado para "acharmos" o arquivo secrets.yml. Explicarei daqui a pouco.

Na linha 2, importamos do Selenium o Webdriver, responsável pela automatização (abertura das páginas e preenchimento dos campos).

Nas próximas duas linhas, importamos as bibliotecas restantes que são responsáveis pelo nosso arquivo secrets.yml, no qual o username e password serão guardados, e pela data que será salva no nome do arquivo final.

Na última linha, importamos o responsável por tirar o screenshot.

import os
from selenium import webdriver
import yaml
from datetime import date
import pyscreenshot as ImageGrab

Neste bloco de código, mostramos ao nosso programa onde está nosso arquivo secrets.yml e o carregamos.

cur_dir = os.path.dirname(os.path.realpath(__file__))
secret_path = os.path.join(cur_dir, 'secrets.yml')

with open(secret_path, 'r') as stream:
    data = yaml.load(stream)
    USERNAME = data.get('user','')
    PASSWORD = data.get('password')

O arquivo secrets.yml é composto por apenas dois campos, "password" e "user", que, PASMEM, são para inserir sua senha e seu usuário.

password: senha_do_zezinho_hacker
user: zezinhohacker123

Nestas três linhas abrimos o Firefox, passamos para ele qual o endereço desejamos acessar e maximizamos a janela, respectivamente.

driver = webdriver.Firefox()
driver.get("https://github.com/login")
driver.maximize_window()

Aqui é onde a "mágica" acontece.

Na primeira linha, a propriedade "find_element_by_id" busca o campo "login_field", onde devemos inserir o nome de usuário. Na linha posterior, enviamos aquele username informado lá no secrets, lembra?

Nas próximas duas linhas, é feito o mesmo procedimento, mas, desta vez, com a senha.

Na última, clicamos o botão para logarmos.

email = driver.find_element_by_id("login_field")
email.send_keys(USERNAME)
senha = driver.find_element_by_id("password")
senha.send_keys(PASSWORD)
driver.find_element_by_name('commit').click()

Nesta linha, nós entramos no nosso perfil do Github.

Quando utilizamos {0}, "guardamos" o espaço, para o que informarmos adiante. Ou seja, no espaço reservado, será inserido o username.

driver.get("https://github.com/{0}" .format(USERNAME))

Por exemplo, se fizermos o seguinte código:

print("Meus esportes preferidos são: {0}, {1} e {2}" .format("futebol", "basquete", "corrida"))

O resultado será:

 Meus esportes preferidos são: futebol, basquete e corrida.

Deu para entender?


Na última linha do programa, salvamos a imagem.

No campo bbox, informamos qual área da tela queremos dar o screenshot, na ordem: X1, Y1, X2, Y2. Você pode alterá-lo de acordo com seu navegador.

No save, utilizamos o que ensinei acima para gerar o arquivo da seguinte maneira: "dataatual_gitshot_nomedousuario".

img = ImageGrab.grab(bbox=(460,540,770,208)).save("{0}_gitshot_{1}.png" .format(date.today(), USERNAME))

Este será o resultado. O nome do arquivo, no meu caso, ficou "2016-01-24_gitshot_othonalberto.png".

Resultado


Código completo:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os

from selenium import webdriver
import yaml
from datetime import date
import pyscreenshot as ImageGrab

cur_dir = os.path.dirname(os.path.realpath(__file__))
secret_path = os.path.join(cur_dir, 'secrets.yml')

with open(secret_path, 'r') as stream:
    data = yaml.load(stream)
    USERNAME = data.get('user','')
    PASSWORD = data.get('password')

driver = webdriver.Firefox()
driver.get("https://github.com/login")
driver.maximize_window()

email = driver.find_element_by_id("login_field")
email.send_keys(USERNAME)
senha = driver.find_element_by_id("password")
senha.send_keys(PASSWORD)
driver.find_element_by_name('commit').click()

driver.get("https://github.com/{0}" .format(USERNAME))

img = ImageGrab.grab(bbox=(460,540,770,208)).save("{0}_gitshot_{1}.png" .format(date.today(), USERNAME))
# bbox=(X1, Y1, X2, Y2)

É isso! Espero ter contribuído com o conhecimento de vocês com este post e gerado curiosidade para que experimentem o Selenium.

Quem quiser contribuir, seja com código ou sugestões, sinta-se à vontade.

Abraços!

por Othon Alberto em 11 de February de 2016 às 13:47

February 10, 2016

Filipe Saraiva

Cantor migrando para o Phabricator: que ferramentas nossos contribuidores devem usar

Projetos e softwares desenvolvidos pela comunidade KDE estão migrando para uma nova ferramenta que irá gerenciar nossos códigos, commits, revisões, tarefas, e mais. Esta ferramenta é o Phabricator e você pode visitar a instância dela para os projetos do KDE neste endereço.

Desde novembro de 2015 estamos migrando o Cantor para o Phabricator. Após nossa primeira revisão de código finalizada com sucesso alguns dias atrás, decidi escrever um post sobre que ferramentas nossos contribuidores devem utilizar enquanto o processo de migração não é finalizado.

Projeto

Phabricator tem uma aplicação para gerenciamento de projetos onde podemos colocar algumas informações úteis e coordenar a execução de tarefas. A página de projeto do Cantor está online e configurada.

Outra interessante funcionalidade é a possibilidade de ingressar em um projeto ou apenas acompanhar suas atividades. Se você tiver uma KDE Identity, faça o login no KDE Phabricator e siga-nos!

Workboard

O KDE oferece uma aplicação para gerenciamento da execução de tarefas utilizando um quadro estilo kanban, o KDE TODO. Apesar dela ser uma excelente ferramenta, nós nunca a utilizamos para o Cantor.

A aplicação Projects no Phabricator tem uma funcionalidade com este mesmo objetivo, o Workboard. Atualmente a estamos utilizando para acompanhar as tarefas do estudante Fernando Telles durante o SoK. A partir dessa experiência, pretendo utilizar o Workboard para gerenciar o desenvolvimento do Cantor.

Tarefas, bugs, desejos

A aplicação do Phabricator chamada Maniphest é uma ferramenta para criação e acompanhamento de bugs, tarefas e desejos (sugestões de funcionalidades).

Entretanto no KDE temos um Bugzilla pesadamente customizado, fazendo com que para o momento não tenhamos uma decisão sobre como migrar nossa ferramenta de relatórios de bugs.

Portanto, KDE Bugzilla ainda é nossa ferramenta para relatórios de bugs. Apesar disso, convido os contribuidores para que usem o Maniphest para submissão de desejos de novas funcionalidades. Nós nunca utilizamos o Bugzilla para este último objetivo, então não haverá problemas se começarmos a utilizar essa nova ferramenta para isso.

Repositório

A exemplo da maioria dos projetos do KDE, Cantor tem seu código fonte gerenciado pelo git. Phabricator tem uma aplicação chamada Diffusion que permite navegar e ver uma variedade de dados a partir de um repositório de código fonte.

Esta aplicação está configurada para o Cantor e pode ser visitada neste link.

Revisão de código

A aplicação do Phabricator para revisão de código é chamada Differential e também está disponível para o Cantor.

Entretanto ainda não há uma decisão sobre a migração definitiva e desativação da ferramenta atual de revisão de código usada pelo KDE, o Reviewboard. Portanto, nossos colaboradores podem utilizar uma ou outra ferramenta (mas por favor nunca as duas ao mesmo tempo!). Ainda assim, fica a recomendação para que deem prioridade e comecem a utilizar o Differential.

Wiki

Sim, Phabricator também tem sua própria aplicação para páginas wiki, chamada Phriction. Atualmente o Cantor só tem uma página na wiki Userbase. Como nós não utilizamos wiki no momento, só decidiremos se iremos utilizar o Phriction em algum momento no futuro.

Comunicação

Ok, o Phabricator também tem uma ferramenta própria para comunicação, Conpherence. Entretanto, os colaboradores do Cantor devem continuar a usar nossas atuais ferramentas de comunicação providas pelo KDE Edu, o canal IRC #kde-edu na rede Freenode e a  lista de e-mail do KDE Edu.

Apesar de eu ter algumas críticas sobre o Phabricator (por exemplo, eu não gosto da arquitetura Aplicações -> Projeto; prefiro Projeto -> Aplicações), ela é uma ferramenta muito interessante para o gerenciamento de projetos e tem uma ampla gama de aplicações para trabalhos específicos. Neste texto eu listei várias delas, mas ainda há muitas outras para serem exploradas e avaliadas.

Espero que este post possa ajudar os colaboradores do Cantor sobre qual ferramenta deve ser utilizada para desempenhar algum trabalho do projeto. Talvez o texto também sirva para apresentar algumas novidades para futuros usuários do Phabricator e ajude desenvolvedores do KDE durante o processo de migração. Por último, principalmente para o público de não colaboradores, o texto apresenta algumas das ferramentas que usamos no KDE.

O impacto do Phabricator na comunidade KDE é algo que deverá ser analisado no futuro próximo. Essa ferramenta e suas diversas aplicações poderão mudar consideravelmente como os subprojetos no KDE são organizados. Vamos ver o que o futuro revelará para nós.

por Filipe Saraiva em 10 de February de 2016 às 16:16