diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 000000000..e5d50d384 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,32 @@ +name: Build Test + +on: + push: + branches: + - '*' + - '!pelican' + +permissions: + contents: read + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout do repositório + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Configura python + uses: actions/setup-python@v4 + with: + python-version: "2.7" + cache: pip + + - name: Instala dependências + run: pip install -r requirements.txt + + - name: Build do site + run: make publish diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml new file mode 100644 index 000000000..a7b555e78 --- /dev/null +++ b/.github/workflows/github-pages.yml @@ -0,0 +1,55 @@ +name: GitHub Pages + +on: + push: + branches: + - pelican + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout do repositório + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Configura python + uses: actions/setup-python@v4 + with: + python-version: "2.7" + cache: pip + + - name: Instala dependências + run: pip install -r requirements.txt + + - name: Build do site + run: make publish + + - name: Upload do site + uses: actions/upload-pages-artifact@v1 + with: + path: output/ + + deploy: + name: Deploy + needs: build + permissions: + pages: write + id-token: write + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy no GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.gitignore b/.gitignore index e1673531f..14885e0ab 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,23 @@ output/ # OS Thumbs.db + +# Arquivos temporarios do Gedit +*~ + +# Cache files +cache/* + +# Pycharm +.idea + +# Virtualenv +venv/ + +MacOS Files +.DS_Store + +# alias +src + +.env diff --git a/.travis.yml b/.travis.yml index 82ab1cde9..87856a85d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ language: python python: - 2.7 install: -- pip install -r requirements.txt --use-mirrors +- pip install -r requirements.txt script: - make publish notifications: @@ -16,5 +16,6 @@ env: global: - secure: fjtVZfyJ7SOg1Glo3T6zYyyzf3OcBiIKY3Lx2abwNt3jIrZalTAc/T2EpeRF+0cLp4NAhHGxylD/pQahWYAw30+SBD5wghd9iBpbJmTxyWHaM9zdLHiU5M3fuSeRwRdIVSG46tLv5wulCIBDubw/b32Ewc4VfEqHtjIahBuGvr4= before_install: -- git submodule update --init --recursive +- git submodule update --remote --merge after_success: bash deploy.sh + diff --git a/README.md b/README.md index 1f25bc75a..74d1c4814 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [pythonclub.com.br][0] ====================== +Duvidas sobre este projeto, deixe sua mensagem em [![Gitter](https://badges.gitter.im/pythonclub/pythonclub.github.io.svg)](https://gitter.im/pythonclub/pythonclub.github.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Blog colaborativo sobre tecnologias que envolvam a linguagem Python @@ -10,9 +11,9 @@ Como Contribuir * Faça um fork desse repositório, clicando no botão [![Fork][14]][15], na parte superior direita da pagina do Github * Clone seu fork: - ``git clone --recursive https://github.com/SEU_USUARIO_DO_GITHUB/pythonclub.github.io.git`` + ``git clone --depth 1 --recursive https://github.com/SEU_USUARIO_DO_GITHUB/pythonclub.github.io.git`` -* Instale os requirements ``pip install -r requirements.txt`` +* Instale os requirements ``pip install -r requirements.txt`` - se você não tiver o pip instalado, instale-o: https://pip.pypa.io/en/latest/installing.html#install-pip * Todas as publicações ficam na pasta ``content``, os textos podem ser escritos no formato **[Markdown][4]** ou **[reStructuredText][5]**, fique a vontade para usar o que você sentir mais afinidade, veja alguns **[exemplos][6]**. @@ -79,17 +80,6 @@ Para finalizar o servidor use: ``./develop_server.sh stop`` -Futuras Publicações -------------------- - -Alguns dos contribuidores criaram o compromisso de publicar alguns artigos. - -Foi estabelecido um prazo maximo para a entrega dos artigos com o intuito de que o contribuidor realmente publique o artigo com o conteudo que ele mesmo definiu. - -Você pode ver a lista contendo os nomes dos artigos nesta planilha no [Google Drive][7]. - -Quando tiver um assunto e uma data de entrega, adicione na planinha, ao finalizar o seu artigo, envie o pull request e atualize a planilha marcando que sua publicação já foi entregue. - [0]: http://pythonclub.com.br/ [1]: https://pages.github.com/ diff --git a/content/a-armadilha-dos-argumentos-com-valores-padrao.md b/content/a-armadilha-dos-argumentos-com-valores-padrao.md new file mode 100644 index 000000000..7408ef65b --- /dev/null +++ b/content/a-armadilha-dos-argumentos-com-valores-padrao.md @@ -0,0 +1,197 @@ +title: A armadilha dos argumentos com valores padrão +Slug: a-armadilha-dos-argumentos-com-valores-padrao +Date: 2015-06-07 11:00 +Tags: python,mutable,function,class,anti-pattern +Author: Diego Garcia +Email: drgarcia1986@gmail.com +Github: drgarcia1986 +Site: http://www.codeforcloud.info +Twitter: drgarcia1986 +Linkedin: drgarcia1986 +Category: anti-patterns + + +
+ +
+
+Algo muito comum em várias linguagens de programação é a possibilidade de definir _valores default_ (valores padrão) para argumentos de funções e métodos, tornando a utilização desses opcional. +Isso é ótimo, principalmente para manter retrocompatibilidade, porém, o python possui uma pequena armadilha que caso passe despercebida, pode causar sérios problemas, muitas vezes difíceis de serem detectados. +Essa armadilha ocorre quando usamos valores de tipos `mutáveis` como valor default de argumentos. + + + +### O que são tipos mutáveis e imutáveis? +Segundo a [documentação oficial do python](https://docs.python.org/3.4/reference/datamodel.html), o valor de alguns objetos pode mudar, esses objetos que podem ter seu valor alterado após serem criados são chamados de mutáveis, enquanto que os objetos que não podem ter seus valores alterados após serem criados são chamados de imutáveis (simples assim). + +* **Tipos mutáveis**: + +Listas, Dicionários e tipos definidos pelo usuário. + +* **Tipos imutáveis**: + +Numeros, Strings e Tuplas. + +> Apesar de serem imutáveis, a utilização de um valor mutável (uma lista por exemplo) dentro de uma tupla, pode causar o efeito _[tuplas mutáveis](http://pythonclub.com.br/tuplas-mutantes-em-python.html)_, onde visualmente o valor da tupla é alterado, mas por trás dos panos o valor da tupla não muda, o que muda é o valor do objeto pelo qual a tupla está se referenciando. + +### A armadilha +Como disse no começo desse blogpost, é muito comum a utilização de valores default em agurmentos de funções e métodos, por essa razão, nos sentimos seguros em fazer algo desse tipo: + +```python +def my_function(my_list=[]): + my_list.append(1) + print(my_list) +``` + +Porém, levando esse exemplo em consideração, o que irá acontecer se invocarmos essa função 3 vezes? + +```python +>>> my_function() +[1] +>>> my_function() +[1, 1] +>>> my_function() +[1, 1, 1] +``` +Sim, o valor do argumento `my_list` mudou em cada vez que executamos a função sem passar algum valor para ele. + +### Por que isso acontece? +Isso acontece porque o python processa os valores default de cada argumentos de uma função (ou método) quando essa for definida, após esse processamento o valor é atribuido ao objeto da função. +Ou seja, por questões de optimização, seguindo nosso exemplo, o python não cria uma lista vazia para o argumento `my_list` a cada vez que a função `my_function` for invocada, ele reaproveita uma lista que foi criada no momento em que essa função foi importada. + +```python +>>> my_function.func_defaults +([],) +>>> id(my_function.func_defaults[0]) +140634243738080 +>>> my_function() +[1] +>>> my_function.func_defaults +([1],) +>>> id(my_function.func_defaults[0]) +140634243738080 +>>> my_function() +[1, 1] +>>> my_function.func_defaults +([1, 1],) +>>> id(my_function.func_defaults[0]) +140634243738080 +``` +> Note que a identificação do argumento (no caso `my_list`) não muda, mesmo executando a função várias vezes. + +Outro exemplo seria utilizar o resultado de funções como valores default de argumentos, por exemplo, uma função com um argumento que recebe como default o valor de `datetime.now()`. + +```python +def what_time_is_it(dt=datetime.now()): + print(dt.strftime('%d/%m/%Y %H:%M:%S')) +``` +O valor do argumento `dt` sempre será o _datetime_ do momento em que o python carregou a função e não o _datetime_ de quando a função foi invocada. + +```python +>>> what_time_is_it() +07/06/2015 08:43:55 +>>> time.sleep(60) +>>> what_time_is_it() +07/06/2015 08:43:55 +``` + +### Isso também acontece com classes? +Sim e de uma forma ainda mais perigosa. + +```python +class ListNumbers(): + def __init__(self, numbers=[]): + self.numbers = numbers + + def add_number(self, number): + self.numbers.append(number) + + def show_numbers(self): + print(numbers) +``` +Assim como no caso das funções, no exemplo acima o argumento `numbers` é definido no momento em que o python importa a classe, ou seja, a cada nova instância da classe `ListNumbers`, será aproveitada a mesma lista no argumento `numbers`. + +```python +>>> list1 = ListNumbers() +>>> list2 = ListNumbers() +>>> list1.show_numbers() +[] +>>> list2.show_numbers() +[] +>>> list2.add_number(1) +>>> list1.show_numbers() +[1] +>>> list2.show_numbers() +[1] +>>> list1.numbers is list2.numbers +True +``` + +### Por que isso não acontece com Strings? +Porque strings são `imutáveis`, o que significa que a cada alteração de valor em uma variavel que armazena uma strings, o python cria uma nova instância para essa variável. + +```python +>>> a = 'foo' +>>> id(a) +140398402003832 +>>> a = 'bar' +>>> id(a) +140398402003872 # o penúltimo número muda :) +``` + +Em argumentos com valores default, não é diferente. + +``` +def my_function(my_str='abc'): + my_str += 'd' + print(my_str) +``` +No exemplo acima, sempre que for executado o `inplace add` (`+=`) será criada outra váriavel para `my_str` sem alterar o valor default do argumento. + +```python +>>> my_function() +abcd +>>> my_function.func_defaults +('abc',) +>>> my_function() +abcd +>>> my_function.func_defaults +('abc',) +``` + +### Como se proteger? +A maneira mais simples de evitar esse tipo de surpresa é utilizar um [valor sentinela](http://en.wikipedia.org/wiki/Sentinel_value) como por exemplo `None`, nos argumentos opcionais que esperam tipos mutáveis: + +```python +def my_function(my_list=None): + if my_list is None: + my_list = [] + my_list.append(1) + print(my_list) +``` + +Ou, para deixar o código ainda mais elegante, podemos simplificar a condicional com um simples `or`: + +```python +def my_function(my_list=None): + my_list = my_list or [] + my_list.append(1) + print(my_list) +``` +> Obrigado [Bruno Rocha](http://pythonclub.com.br/author/bruno-cezar-rocha.html) pela sugestão. + +Pronto, sem surpresas e sem armadilhas :). + +```python +>>> my_function() +[1] +>>> my_function() +[1] +>>> my_function() +[1] +``` + +### Referências + +* [Fluent Python (Mutable types as parameter defaults: bad idea)](http://shop.oreilly.com/product/0636920032519.do) +* [Python Anti-Patterns (Using a mutable default value as an argument)](http://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html) diff --git a/content/abrangencia-de-listas-e-dicionarios.md b/content/abrangencia-de-listas-e-dicionarios.md new file mode 100644 index 000000000..d025ac785 --- /dev/null +++ b/content/abrangencia-de-listas-e-dicionarios.md @@ -0,0 +1,131 @@ +Title: Abrangência de Listas e Dicionários +Slug: abrangencia-de-listas-e-dicionarios-com-python +Date: 2017-01-16 10:37:39 +Category: Python +Tags: python,tutorial,list comprehensions +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +A utilização de listas em Python é algo trivial. A facilidade provida pela linguagem aliada a simplicidade da estrutura de dados *list* a torna, ao lado dos dicionários *dict*, uma das estrutura de dados mais utilizadas em Python. Aqui neste tutorial irei compartilhar algo que aprendi trabalhando com listas e dicionário em Python, mais especificamente no que diz respeito a *abrangência* de listas (e dicionários). + +## Abrangência de listas + +A abrangência de listas, ou do inglês *list comprehensions*, é um termo utilizado para descrever uma sintaxe compacta que o Python nos oferece para criamos uma lista baseada em outra lista. Pareceu confuso? Ok, vamos aos exemplos! + +### Exemplo 1 +Vamos supor que temos a seguinte lista de valores: + +```python +valores = [1, 2, 3, 4, 5] +``` +Queremos gerar uma outra lista contendo o dobro de cada um desses números, ou seja, + +```python +[2, 4, 6, 8, 10] +``` +Inicialmente, podemos montar o seguinte código como solução: + +```python +# Recebe o nosso resultado +valores_dobro = [] + +for val in valores: + valores_dobro.append(val * 2) + +print(valores_dobro) + +>>> +[2, 4, 6, 8, 10] + +``` + +A solução acima é uma solução simples e resolve nosso problema, entretanto para algo tão simples precisamos de 4 linhas de código. Este exemplo é uma situação onde a *abrangência de lista* pode ser útil. Podemos compactar a criação da lista `valores_dobro` da seguinte maneira: + +```python +valores_dobro = [valor*2 for valor in valores] +``` +Bacana não? O exemplo seguinte podemos incrementar mais o exemplo acima. + +### Exemplo 2 + +Vamos supor que desejamos criar uma lista onde apenas os valores pares (resto da divisão por 2 é zero) serão multiplicados por 2. Abaixo temos a nossa lista de valores: + +```python +valores = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +``` + +Assim como no exemplo anterior, podemos resolver utilizando um algoritmo básico. + +```python +# Lista que recebera o nosso resultado +valores_dobro = [] + +for valor in valores: + if valor % 2 == 0: + valores_dobro.append(valor * 2) + +print(valores_dobro) + +>>> +[4, 8, 12, 16, 20] + +``` +Podemos também resolver o mesmo problema utilizando as funções nativas *map* e *filter*: + +```python +valores_dobro = map(lambda valor: valor * 2, filter(lambda valor: valor % 2 == 0, valores)) +``` +Muito mais complicada não é? Apesar de resolver nosso problema, expressões como a acima são difíceis de ler e até mesmo de escrever. Em casos como esse, podemos novamente compactar nosso algoritmo utilizando a *abrangência de lista*. + +```python +valores_dobro = [valor * 2 for valor in valores if valor % 2 == 0] +``` +Muito mais simples, não? Vamos para o próximo exemplo. + +### Exemplo 3 + +De maneira semelhante a lista, nós também podemos aplicar a abrangência em lista e dicionários. Segue um exemplo onde temos o seguinte dicionário: + +```python + dicionario = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6} +``` + +Vamos criar um segundo dicionário contendo apenas as chaves que são consoantes, ou seja, `b`, `c`, `d` e `f`, sendo que o valor para cada uma dessas chaves deve ser o dobro do valor armazenado na respectiva chave do dicionário original. Complicado? Em outras palavras, o novo dicionário deve ficar assim: + +```python + novo_dicionario = {'b': 4, 'c': 6, 'd': 8, 'f': 12} +``` + +Utilizando um algoritmo genérico, podemos resolver o problema da seguinte maneira: + +```python +novo_dicionario = {} + +for chave, valor in dicionario: + if chave in ['b', 'c', 'd', 'f']: + novo_dicionario[chave] = 2 * valor + +print(novo_dicionario) + +>> +{'b': 4, 'c': 6, 'd': 8, 'f': 12} + +``` +Aplicando agora a abrangência, conseguimos compactar o código acima de maneira interessante: + +```python +novo_dicionario = {chave: 2 * valor for chave, valor in dicionario.items() if chave in ['b', 'c', 'd', 'f']} +``` + +## Conclusão + +Chegamos ao final de mais um tutorial! Sempre temos de ter em mente que tão importante quanto escrever um código que funciona, é mantê-lo (seja por você ou por outro programador). Neste ponto, a abrangência de lista (e outras estruturas de dados) nos ajudam a escrever um código claro e fácil de dar manutenção. + +Até o próximo tutorial pessoal! + +## Referências + +* [Python eficaz: 59 maneiras de programar melhor em Python; Slatkin, Brett; Novatec Editora, 2016.](https://novatec.com.br/livros/python-eficaz/) diff --git a/content/algoritmos_ordenacao_usando_python.rst b/content/algoritmos_ordenacao_usando_python.rst new file mode 100644 index 000000000..71c714302 --- /dev/null +++ b/content/algoritmos_ordenacao_usando_python.rst @@ -0,0 +1,134 @@ +Algoritmos de Ordenação +######################## + +:date: 2018-11-29 13:10 +:tags: python, algoritmos +:category: Python +:slug: algoritmos-ordenacao +:author: Lucas Magnum +:email: lucasmagnumlopes@gmail.com +:github: lucasmagnum +:linkedin: lucasmagnum + +Fala pessoal, tudo bom? + +Nos vídeos abaixo, vamos aprender como implementar alguns dos algoritmos de ordenação usando Python. + + +Bubble Sort +=========== + +Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Doy64STkwlI `_. + + +.. youtube:: Doy64STkwlI + +Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=B0DFF0fE4rk `_. + +.. youtube:: B0DFF0fE4rk + +Código do algoritmo + +.. code-block:: python + + def sort(array): + + for final in range(len(array), 0, -1): + exchanging = False + + for current in range(0, final - 1): + if array[current] > array[current + 1]: + array[current + 1], array[current] = array[current], array[current + 1] + exchanging = True + + if not exchanging: + break + + +Selection Sort +============== + +Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=vHxtP9BC-AA `_. + +.. youtube:: vHxtP9BC-AA + +Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=0ORfCwwhF_I `_. + +.. youtube:: 0ORfCwwhF_I + +Código do algoritmo + +.. code-block:: python + + def sort(array): + for index in range(0, len(array)): + min_index = index + + for right in range(index + 1, len(array)): + if array[right] < array[min_index]: + min_index = right + + array[index], array[min_index] = array[min_index], array[index] + + +Insertion Sort +============== + +Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=O_E-Lj5HuRU `_. + +.. youtube:: O_E-Lj5HuRU + +Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Sy_Z1pqMgko `_. + +.. youtube:: Sy_Z1pqMgko + +Código do algoritmo + +.. code-block:: python + + def sort(array): + for p in range(0, len(array)): + current_element = array[p] + + while p > 0 and array[p - 1] > current_element: + array[p] = array[p - 1] + p -= 1 + + array[p] = current_element + + +Merge Sort +============== + +Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Lnww0ibU0XM `_. + +.. youtube:: Lnww0ibU0XM + + +Como implementar o algoritmo usando Python - Parte I: `https://www.youtube.com/watch?v=cXJHETlYyVk `_. + +.. youtube:: cXJHETlYyVk + +Código do algoritmo + +.. code-block:: python + + def sort(array): + sort_half(array, 0, len(array) - 1) + + + def sort_half(array, start, end): + if start >= end: + return + + middle = (start + end) // 2 + + sort_half(array, start, middle) + sort_half(array, middle + 1, end) + + merge(array, start, end) + + + def merge(array, start, end): + array[start: end + 1] = sorted(array[start: end + 1]) + diff --git a/content/aprendendo_e_ensinando_python.md b/content/aprendendo_e_ensinando_python.md index 04836f15c..650ebb6e5 100644 --- a/content/aprendendo_e_ensinando_python.md +++ b/content/aprendendo_e_ensinando_python.md @@ -39,7 +39,7 @@ a semana e aos sábados e domingos eu produzia uma nova aula com novos desafios e exercícios para quem estava acompanhando. Algo muito legal (ou nem tanto) que eu fiz na época foi pedir para o pessoal me mandar os scripts que faziam, assim eu podia corrigir e ajudar ainda mais. Talvez essa ideia não tenha sido tão boa -pois, não tenho os números agora, mas na época das publicações eu chagava a +pois, não tenho os números agora, mas na época das publicações eu chegava a receber mais de 100 e-mails por semana com scripts de pessoas de todas as partes do Brasil (Alguns de fora). Imagina o trabalhão que dava para conseguir acompanhar a todos! diff --git a/content/bot-telegram-e-web-scraping-parte1.md b/content/bot-telegram-e-web-scraping-parte1.md new file mode 100644 index 000000000..a4bb33885 --- /dev/null +++ b/content/bot-telegram-e-web-scraping-parte1.md @@ -0,0 +1,114 @@ +Title: Bot telegram mais web scraping - parte 1 +Slug: bot-telegram-mais-web-scraping-parte-1 +Date: 2016-10-23 20:30 +Tags: python,blog,tutorial,aulas +Author: Pedro Souza. +About_author: Just another Programmer and Security Researcher, just a noob., +Email: souza.vipedro@gmail.com +Github: Pedro-Souza +Facebook: https://www.facebook.com/DeveloperPS +Category: Python, Bot, Telegram, Scraping + + +Irei separa o artigo em 2 partes para não ficar extenso. Nessa primeira +parte irei falar um pouco como criar um bot no telegram e como +programa-lo para nos responder. + +1 - Parte 1 - [**Bot simples.**]({filename}bot-telegram-e-web-scraping-parte1.md) (você está aqui) + +2 - Parte 2 - **Bot e Web Scraping** + +Primeiro de tudo precisamos cria o bot, para isso usamos o próprio bot +do telegram que faz isso para gente. Para isso bastar iniciar uma +conversa com o [@BotFather](https://web.telegram.org/#/im?p=@BotFather), ele irá nós da algumas +opções: + +``` +/newbot - create a new bot +/token - generate authorization token +/revoke - revoke bot access token +/setname - change a bot's name +/setdescription - change bot description +/setabouttext - change bot about info +/setuserpic - change bot profile photo +/setinline - change inline settings +/setinlinegeo - toggle inline location requests +/setinlinefeedback - change inline feedback settings +/setcommands - change bot commands list +/setjoingroups - can your bot be added to groups? +/setprivacy - what messages does your bot see in groups? +/deletebot - delete a bot +/newgame - create a new game +/listgames - get a list of your games +/editgame - edit a game +/deletegame - delete an existing game +/cancel - cancel the current operation + +``` + +As que nós interessa por enquanto são: + +``` +/newbot - Cria um novo bot. +/setdescription - Adiciona uma descrição ao nosso bot. +/setuserpic - Adiciona uma imagem ao nosso bot. +``` + +Feito isso agora temos um token, que iremos usar para dar funções e vida +ao bot. Para isso iremos usar a lib telegram-bot, ela irá facilitar a +nosso vida, assim não iremos precisar mexer diretamente com a API do +telegram. + +### Instalando telegram-bot utilizando o pip + +```bash +pip install python-telegram-bot +``` + +Agora com a biblioteca instalada iremos programar um mini bot para nós falar as horas. + +```python + +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- + +from telegram.ext import Updater, CommandHandler +from time import strftime + +up = Updater('Insira o token aqui.') + + +def Horas(bot, update): + + msg = "Olá {user_name} agora são: " + msg += strftime('%H:%M:%S') + + bot.send_message(chat_id=update.message.chat_id, + text=msg.format( + user_name=update.message.from_user.first_name)) + + +up.dispatcher.add_handler(CommandHandler('horas', Horas)) +up.start_polling() + +``` + +### Entendendo o código. + +1 - Importamos tudo que iremos utilizar.
+2 - Informamos o token do nosso bot.
+3 - Criamos uma função que pega a horas com strftime e responde no chat.
+4 - Criamos um comando para o nosso bot, no caso o /horas.
+5 - Startamos o bot.
+ +Feito isso quando mandar um /horas para o bot ele irá nos responder com: "Olá SeuNome agora são +Horas." + +Caso você queira adicionar mais funções ao bot, +[aqui](http://python-telegram-bot.readthedocs.io/en/latest/) está a documentação da biblioteca. + +Na próxima parte iremos escolher alguns site que fale sobre Python e fazer Scraping nele, assim +sempre que ele tiver uma nova postagem nosso bot vai nós enviar uma mensagem informando. + + + diff --git a/content/bottle-framework-full-stack-sem-ser-o-django.md b/content/bottle-framework-full-stack-sem-ser-o-django.md new file mode 100644 index 000000000..b33d48852 --- /dev/null +++ b/content/bottle-framework-full-stack-sem-ser-o-django.md @@ -0,0 +1,146 @@ +Title: Bottle Framework full stack sem Django +Slug: bottle-framework-full-stack-sem-django +Date: 2014-12-03 19:40 +Tags: bottle,python +Author: Eric Hideki +Email: eric8197@gmail.com +Github: erichideki +Site: http://ericstk.wordpress.com +Twitter: erichideki +Category: begginers, bottle, tutorial + +#Esse artigo foi originalmente traduzido de: + +[http://www.avelino.xxx/2014/12/bottle-full-stack-without-django] + +Este artigo é baseado em uma palestra que apresentei aqui no Brasil, seguem os [slides](https://speakerdeck.com/avelino/bottle-o-full-stack-sem-django)! + +![Bottle micro framework web](/images/erichideki/bottle.png) + +Bottle é um micro framework web compatível com WSGI, depende apenas da biblioteca padrão do Python, sendo compatível com Python 2.6, 2.7, 3.2, 3.3 e 3.4, [sendo um arquivo único](https://github.com/defnull/bottle/blob/master/bottle.py). Ele foi criado pelo Marcel Hellkamp ([@defnull](https://github.com/defnull)) e mantido pela [comunidade](https://github.com/orgs/bottlepy/people) que mantém esse framework. + +[Django](https://www.djangoproject.com/) é um framework para rápido desenvolvimento na web, escrito em Python, no qual usa o padrão MTV(model-template-view), sendo pragmático. Foi originalmente criado como um sistema de gerenciamento de um site jornalístico na cidade de Lawrence, Kansas. Se tornou um projeto open-source e foi publicado sobre a licença BSD em 2005. O nome Django foi inspirado pelo músido de jazz Django Reinhardt. Django se tornou muito conhecido pelas suas baterias inclusas, i.e diversas bibliotecas distribuídas que se juntaram ao centro do framework para simplificar o trabalho (chamado "Full stack"). + +Pragmatismo é o que contém a prática, considerações realistas, com objetivos bem definidos. Ser pragmático é ser prático tendo objetivos definidos. Em outras palavras, o time que desenvolve o Django toma algumas modelagens de arquitetura e quem usa Django segue essa arquitetura sem ser capaz de mudá-la facilmente. + +Isto é bom para um framework web que tem baterias inclusas? Depente, se você usa tudo o que o framework oferece, sim, mas nem todos os designs de aplicações são iguais. + +Muitos projetos não usam 80% do que Django oferece, nesses casos em que não usam mais que 50%, o custo que pagamos ao oferecer o Django a alguém é alto, já que temos definido a arquitetura, ou seja, perde-se a performance porque o Django tem diversos módulos que não serão usados e obrigatoriamente subirá alguns módulos que não iremos usar. Quando nós usamos um micro framework, fazemos toda a arquitetura da aplicação, então não temos previamente preparado a arquitetura para desenvolver o necessário, dedicando o tempo do time para definir a arquitetura da aplicação. + +Todos os pacotes que nós temos na biblioteca padrão do Python/Django podem ser substituídas usando um micro framework! + +* ORM - [SQLAlchemy](http://www.sqlalchemy.org/) to [bottle-sqlalchemy](https://github.com/iurisilvio/bottle-sqlalchemy) +* Forms - [WTForms](https://wtforms.readthedocs.org/en/latest/) +* Template Engine - [Jinja2](http://jinja.pocoo.org/docs/dev/), [mako](http://www.makotemplates.org/), etc +* Migration - [Alembic](http://alembic.readthedocs.org/en/latest/) + + +## SQLAlchemy + +O SQLAlchemy existe antes do Django, [sim, antes do Django](https://github.com/zzzeek/sqlalchemy/commit/ec052c6a1f1fb0236bd367c510d82f076cb67bc9) e desde 2005 temos um time focado no desenvolvimento da ORM, ao contrário do Django que dispṍe tempo cuidando do framework web + ORM (Eu acredito que eu não preciso falar com um desenvolvedor focado render mais do que um desenvolvedor não focado). + +Estrutura de um modelo: + +```python +class Entity(Base): + __tablename__ = 'entity' + id = Column(Integer, Sequence('id_seq'), primary_key=True) + name = Column(String(50)) + + def __init__(self, name): + self.name = name + + def __repr__(self): + return "" % (self.id, self.name) +``` + +## WTForms + +A solução alternativa para aqueles que não usam Django e precisam trabalhar com formulários, nós temos o WTForms, que foi criado em [2008](https://github.com/wtforms/wtforms/commit/c0998bac1a4d5cd5fdf43a825529a64e24dea9a5) e atualizado ainda hoje! + +Estrutura de um formulário: + +```python +class UserForm(Form): + name = TextField(validators=[DataRequired(), Length(max=100)]) + email = TextField(validators=[DataRequired(), Length(max=255)]) +``` + +## Mecanismo de template + +Jinja2 é um moderno e contém um design de template amigável para Python, modelado após os templates do Django. É rápido, amplamente usado e seguro com a opcional área restrita de template no ambiente de desenvolvimento. + +Estrutura de um template: + +```html +{% block title %}{% endblock %} + +``` + +## Migrações + +A utilização do Alembic começa com a criação do ambiente de migração. Este é o diretório de arquivos que especificam para uma particular aplicação. O ambiente de migração é criado apenas uma vez, e é mantido ao longo do desenvolvimento do código da aplicação. + +Estrutura de uma migração: + +```python +revision = '1975ea83b712' +down_revision = None + +from alembic import op +import sqlalchemy as sa + +def upgrade(): + pass + +def downgrade(): + pass +``` + +Como criar a evolução e o rebaixamento: + +```python +def upgrade(): + op.create_table( + 'account', + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('name', sa.String(50), nullable=False), + sa.Column('description', sa.Unicode(200)), + ) + +def downgrade(): + op.drop_table('account') +``` + +Estrutura de alteração de tabela: + +```python +""" +$ alembic revision -m "Add a column" +""" + +revision = 'ae1027a6acf' +down_revision = '1975ea83b712' + +from alembic import op +import sqlalchemy as sa + +def upgrade(): + op.add_column('account', sa.Column('last_transaction_date', sa.DateTime)) + +def downgrade(): + op.drop_column('account', 'last_transaction_date') +``` + +## Conclusão + +Exatamente o que você vê, tudo o que o Django contém temos fora do conjunto do Django. Eu não escreveria esse artigo para falar mal do Django, e sim mostrar que existem outras soluções para desenvolvimento full stack. Muitas pessoas usam o Django mas não entendem o ambiente Python, hoje o Django traz muitas coisas preparadas que fazem alguns desenvolvedores serem preguiçosos e não adquirir experiência em arquitetura de software. + +Venha ajudar o Bottle, somos uma comunidade em crescimento, para contribuir com o código do Bottle, olhe essa issue que nós abrimos. Em caso de dúvidas, nós temos uma lista de e-mail e um canal IRC. + +[Se envolva!](http://bottlepy.org/docs/dev/development.html#get-involved) +[http://www.avelino.xxx/2014/12/bottle-full-stack-without-django]:http://www.avelino.xxx/2014/12/bottle-full-stack-without-django diff --git a/content/changing-tuples-pt_BR.rst b/content/changing-tuples-pt_BR.rst new file mode 100644 index 000000000..f8f5d567e --- /dev/null +++ b/content/changing-tuples-pt_BR.rst @@ -0,0 +1,155 @@ +Tuplas mutantes em Python +========================= + + +:date: 2015-02-24 10:17 +:tags: python,tuplas +:category: python-por-dentro +:slug: tuplas-mutantes-em-python +:author: Luciano Ramalho +:email: luciano@ramalho.org +:about_author: Ramalho é autor do livro Fluent Python (O'Reilly, 2014) e sócio/professor em Python.pro.br, oferecendo cursos in-company e também online. Foi diretor técnico do Brasil Online, primeiro portal da Abril na Web, lançado uma semana antes do UOL. Depois liderou times para os sites IDG Now, BOL, UOL, AOL Brasil e outros, usando Python desde 1998. Palestrante em eventos internacionais como PyCon US, OSCON e FISL, ajudou a criar a Associação Python Brasil e foi seu presidente. É membro da Python Software Foundation e fundador do Garoa Hacker Clube, o primeiro hackerspace do Brasil. +:github: ramalho +:site: https://adm.python.pro.br/ +:twitter: ramalhoorg +:linkedin: lucianoramalho + + +Por Luciano Ramalho, autor do livro `Fluent Python`_ (O'Reilly, 2014) + + See also the original article in English: http://radar.oreilly.com/2014/10/python-tuples-immutable-but-potentially-changing.html + + +Tuplas em Python têm uma característica surpreendente: elas são imutáveis, mas seus valores podem mudar. Isso pode acontecer quando uma ``tuple`` contém uma referência para qualquer objeto mutável, como uma ``list``. Se você precisar explicar isso a um colega iniciando com Python, um bom começo é destruir o senso comum sobre variáveis serem como caixas em que armazenamos dados. + +Em 1997 participei de um curso de verão sobre Java no MIT. A professora, Lynn Andrea Stein - uma premiada educadora de ciência da computação - enfatizou que a habitual metáfora de "variáveis como caixas" acaba atrapalhando o entendimento sobre variáveis de referência em linguagens OO. Variáveis em Python são como variáveis de referência em Java, portanto é melhor pensar nelas como etiquetas afixadas em objetos. + +Eis um exemplo inspirado no livro *Alice Através do Espelho e O Que Ela Encontrou Por Lá*, de Lewis Carroll. + +.. image:: {filename}images/ramalho/Tweedledum-Tweedledee_500x390.png + :alt: imagem Alice Através do Espelho e O Que Ela Encontrou Por Lá + +Tweedledum e Tweedledee são gêmeos. Do livro: “Alice soube no mesmo instante qual era qual porque um deles tinha 'DUM' bordado na gola e o outro, 'DEE'”. + +.. image:: {filename}images/ramalho/diagrams/dum-dee.png + :alt: exemplo 1 + +Vamos representá-los como tuplas contendo a data de nascimento e uma lista de suas habilidades: + +.. code-block:: python + + >>> dum = ('1861-10-23', ['poesia', 'fingir-luta']) + >>> dee = ('1861-10-23', ['poesia', 'fingir-luta']) + >>> dum == dee + True + >>> dum is dee + False + >>> id(dum), id(dee) + (4313018120, 4312991048) + + +É claro que ``dum`` e ``dee`` referem-se a objetos que são iguais, mas que não são o mesmo objeto. Eles têm identidades diferentes. + +Agora, depois dos eventos testemunhados por Alice, Tweedledum decidiu ser um rapper, adotando o nome artístico T-Doom. Podemos expressar isso em Python dessa forma: + +.. code-block:: python + + >>> t_doom = dum + >>> t_doom + ('1861-10-23', ['poesia', 'fingir-luta']) + >>> t_doom == dum + True + >>> t_doom is dum + True + +Então, ``t_doom`` e ``dum`` são iguais - mas Alice acharia tolice dizer isso, porque ``t_doom`` e ``dum`` referem-se à mesma pessoa: ``t_doom is dum``. + +.. image:: {filename}images/ramalho/diagrams/dum-t_doom-dee.png + :alt: exemplo 2 + +Os nomes ``t_doom`` e ``dum`` são apelidos. O termo em inglês "alias" significa exatamente apelido. Gosto que os documentos oficiais do Python muitas vezes referem-se a variáveis como “nomes“. Variáveis são nomes que damos a objetos. Nomes alternativos são apelidos. Isso ajuda a tirar da nossa mente a ideia de que variáveis são como caixas. Qualquer um que pense em variáveis como caixas não consegue explicar o que vem a seguir. + +Depois de muito praticar, T-Doom agora é um rapper experiente. Codificando, foi isso o que aconteceu: + +.. code-block:: python + + >>> skills = t_doom[1] + >>> skills.append('rap') + >>> t_doom + ('1861-10-23', ['poesia', 'fingir-luta 'rap']) + >>> dum + ('1861-10-23', ['poesia', 'fingir-luta 'rap']) + +T-Doom conquistou a habilidade ``rap``, e também Tweedledum — óbvio, pois eles são um e o mesmo. Se ``t_doom`` fosse uma caixa contendo dados do tipo ``str`` e ``list``, como você poderia explicar que uma inclusão à lista ``t_doom`` também altera a lista na caixa ``dum``? Contudo, é perfeitamente plausível se você entende variáveis como etiquetas. + +A analogia da etiqueta é muito melhor porque apelidos são explicados mais facilmente como um objeto com duas ou mais etiquetas. No exemplo, ``t_doom[1]`` e ``skills`` são dois nomes dados ao mesmo objeto da lista, da mesma forma que ``dum`` e ``t_doom`` são dois nomes dados ao mesmo objeto da tupla. + +Abaixo está uma ilustração alternativa dos objetos que representam Tweedledum. Esta figura enfatiza o fato de a tupla armazenar referências a objetos, e não os objetos em si. + +.. image:: {filename}images/ramalho/diagrams/dum-skills-references.png + :alt: exemplo 3 + +O que é imutável é o conteúdo físico de uma tupla, que armazena apenas referências a objetos. O valor da lista referenciado por ``dum[1]`` mudou, mas a identidade da lista referenciada pela tupla permanece a mesma. Uma tupla não tem meios de prevenir mudanças nos valores de seus itens, que são objetos independentes e podem ser encontrados através de referências fora da tupla, como o nome ``skills`` que nós usamos anteriormente. Listas e outros objetos imutáveis dentro de tuplas podem ser alterados, mas suas identidades serão sempre as mesmas. + +Isso enfatiza a diferença entre os conceitos de identidade e valor, descritos em *Python Language Reference*, no capítulo `Data model`_: + + Cada objeto tem uma identidade, um tipo e um valor. A identidade de um objeto nunca muda, uma vez que tenha sido criado; você pode pensar como se fosse o endereço do objeto na memória. O operador ``is`` compara a identidade de dois objetos; a função ``id()`` retorna um inteiro representando a sua identidade. + +Após ``dum`` tornar-se um rapper, os irmãos gêmeos não são mais iguais: + +.. code-block:: python + + >>> dum == dee + False + +Temos aqui duas tuplas que foram criadas iguais, mas agora elas são diferentes. + +O outro tipo interno de coleção imutável em Python, ``frozenset``, não sofre do problema de ser imutável mas com possibilidade de mudar seus valores. Isso ocorre porque um ``frozenset`` (ou um ``set`` simples, nesse sentido) pode apenas conter referências a objetos ``hashable`` (objetos que podem ser usados como chave em um dicionário), e o valor destes objetos, por definição, nunca pode mudar. + +Tuplas são comumente usadas como chaves para objetos ``dict``, e precisam ser ``hashable`` - assim como os elementos de um conjunto. Então, as tuplas são ``hashable`` ou não? A resposta certa é **algumas** tuplas são. O valor de uma tupla contendo um objeto mutável pode mudar, então uma tupla assim não é ``hashable``. Para ser usada como chave para um ``dict`` ou elemento de um ``set``, a tupla precisa ser constituída apenas de objetos ``hashable``. Nossas tuplas de nome ``dum`` e ``dee`` não são ``hashable`` porque cada elemento contem uma referência a uma lista, e listas não são ``hashable``. + +Agora vamos nos concentrar nos comandos de atribuição que são o coração de todo esse exercício. + +A atribuição em Python nunca copia valores. Ela apenas copia referências. Então quando escrevi ``skills = t_doom[1]``, não copiei a lista referenciada por ``t_doom[1]``, apenas copiei a referência a ela, que então usei para alterar a lista executando ``skills.append('rap')``. + +Voltando ao MIT, a Profa. Stein falava sobre atribuição de uma forma muito cuidadosa. Por exemplo, ao falar sobre um objeto gangorra em uma simulação, ela dizia: “A variável ``g`` é atribuída à gangorra“, mas nunca “A gangorra é atribuída à variável ``g`` “. Em se tratando de variáveis de referência, é mais coerente dizer que a variável é atribuída ao objeto, e não o contrário. Afinal, o objeto é criado antes da atribuição. + +Em uma atribuição como ``y = x * 10``, o lado direito é computado primeiro. Isto cria um novo objeto ou retorna um já existente. Somente após o objeto ser computado ou retornado, o nome é atribuído a ele. + +Eis uma prova disso. Primeiro criamos uma classe ``Gizmo``, e uma instância dela: + +.. code-block:: python + + >>> class Gizmo: + ... def __init__(self): + ... print('Gizmo id: %d' % id(self)) + ... + >>> x = Gizmo() + Gizmo id: 4328764080 + +Observe que o método ``__init__`` mostra a identidade do objeto tão logo criado. Isso será importante na próxima demonstração. + +Agora vamos instanciar outro ``Gizmo`` e imediatamente tentar executar uma operação com ele antes de atribuir um nome ao resultado: + +.. code-block:: python + + >>> y = Gizmo() * 10 + Gizmo id: 4328764360 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int' + >>> 'y' in globals() + False + +Este trecho mostra que o novo objeto foi instanciado (sua identidade é ``4328764360``) mas antes que o nome ``y`` possa ser criado, uma exceção ``TypeError`` abortou a atribuição. A verificação ``'y' in globals()`` prova que não existe o nome global ``y``. + +Para fechar: sempre leia lado direito de uma atribuição primero. Ali o objeto é computado ou retornado. Depois disso, o nome no lado esquerdo é vinculado ao objeto, como uma etiqueta afixada nele. Apenas esqueça aquela idéia de variáveis como caixas. + +Em relação a tuplas, certifique-se que elas apenas contenham referências a objetos imutáveis antes de tentar usá-las como chaves em um dicionário ou itens em um ``set``. + + Este texto foi originalmente publicado no `blog`_ da editora O'Reilly em inglês. A tradução para o português foi feita por Paulo Henrique Rodrigues Pinheiro. O conteúto é baseado no capítulo 8 do meu livro `Fluent Python`_. Esse capítulo, intitulado *Object references, mutability and recycling* também aborda a semântica da passagem de parâmetros para funções, melhores práticas para manipulação de parâmetros mutáveis, cópias rasas (*shallow copies*) e cópias profundas (*deep copies*), e o conceito de referências fracas (*weak references*) - além de outros tópicos. O livro foca em Python 3 mas grande parte de seu conteúdo se aplica a Python 2.7, como tudo neste texto. + +.. _blog: http://radar.oreilly.com/2014/10/python-tuples-immutable-but-potentially-changing.html +.. _Fluent Python: http://shop.oreilly.com/product/0636920032519.do +.. _Data Model: https://docs.python.org/3/reference/datamodel.html#objects-values-and-types + diff --git a/content/class-based-views-no-django.md b/content/class-based-views-no-django.md new file mode 100644 index 000000000..e93d5cade --- /dev/null +++ b/content/class-based-views-no-django.md @@ -0,0 +1,906 @@ +Title: Class Based Views no Django +Slug: class-based-views-django +Date: 2015-10-26 14:50 +Tags: python,blog,tutorial,django,cbv +Author: Raphael Passini Diniz +Email: raphapassini@gmail.com +Github: raphapassini +Bitbucket: raphapassini +Twitter: raphapassini +Category: Django + +Esse tutorial tem como objetivo explicar o básico sobre Class Based Views +no Django. Por motivos de agilidade vou usar ``CBV`` para me referir as +``Class Based Views``. + +Segundo a documentação do Django sobre CBV: + +> CBV's permitem você estruturar as suas views e reutilizar código aproveitando + heranças e mixins + +O Django já vem CBV's genéricas que atendem as necessidades da maioria das aplicações. +Essas views genéricas são flexiveis o suficiente para você poder adaptá-las as +suas necessidades. + +Nesse tutorial eu vou falar brevemente sobre os 4 grupos de CBV's que existem +no Django atualmente: + +- [Base views](#base_views) + - [View](#view) + - [TemplateView](#template_view) + - [RedirectView](#redirect_view) +- [Display views](#display_views) + - [DetailView](#detail_view) + - [ListView](#list_view) +- [Editing views](#editing_views) + - [Model based Views](#model_view) + - [CreateView, UpdateView, DeleteView](#create_update_delete_view) +- [Date views](#date_views) + - [ArchiveView](#archive_view) + - [YearView](#year_view) + - [MonthView](#month_view) + - [WeekView](#week_view) + - [DayView](#day_view) + - [TodayView](#today_view) + - [DateDetailView](#date_detail_view) +- [Conclusão](#conclusao) +- [Referências](#ref) + + Antes de começarmos a falar sobre as CBV's vamos ver como apontar uma rota + do Django para uma CBV: + +```python + +from django.conf.urls import url +from django.views.generic import TemplateView +from meuapp.views import AboutView + +urlpatterns = [ + url(http://github.com/r'%5Eabout/',%20AboutView.as_view()), +] +``` + +## Base Views + +As classes listadas abaixo contém muito da funcionalidade necessária para criar +views no Django. Essas classes são a base sob a qual as outras CBV's são +construídas. + +### View + +A classe genérica *master*. **Todas** as outras classes herdam dessa classe. +O fluxo básico de execução dessa classe quando recebe uma requisição é: + +1. ``dispatch()`` +2. ``http_method_not_allowed()`` +3. ``options()`` + +A função ``dispatch()`` verifica se a classe tem um método com o nome do verbo +HTTP usado na requisição. Caso não haja, um ``http.HttpResponseNotAllowed`` é +retornado. + +Essa classe sempre responde a requisições com o verbo ``OPTIONS`` retornando +nesse caso uma lista com os verbos suportados. A não ser que o método +``options()`` seja sobrescrito. + +Um exemplo de implementação: + +```python + +from django.http import HttpResponse +from django.views.generic import View + +class MyView(View): + + def get(self, request, *args, **kwargs): + return HttpResponse('Hello, World!') +``` + +No exemplo acima a classe só responde a requisições do tipo ``GET`` e +``OPTIONS``, todas as outras requisições retornam +``http.HttpResponseNotAllowed``. + +### Template View + +Renderiza um template. O fluxo básico de execução dessa classe quando recebe +uma requisição é: + +1. ``dispatch()`` +2. ``http_method_not_allowed()`` +3. ``get_context_data()`` + +Quando você precisa apenas renderizar uma página para o usuário essa com certeza +é a melhor CBV para o caso. Você pode editar o contexto que o template recebe +sobrescrevendo a função ``get_context_data()`` + +Um exemplo de implementação: + +```python + +from django.views.generic.base import TemplateView + +from articles.models import Article + +class HomePageView(TemplateView): + + template_name = "home.html" + + def get_context_data(self, **kwargs): + context = super(HomePageView, self).get_context_data(**kwargs) + context['latest_articles'] = Article.objects.all()[:5] + return context +``` + +No exemplo acima o template *home.html* será renderizado e vai receber como +contexto uma variável chamada ``lastest_articles``. + +Uma coisa interessante é que o contexto da ``TemplateView`` é populado pelo +[ContextMixin](https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins-simple/#django.views.generic.base.ContextMixin) +esse mixin pega automaticamente os argumentos da URL que serviu a View. + +Considere por exemplo: + +```python + +from django.conf.urls import patterns, url +from .views import HelloView + +urlpatterns = patterns( + '', + url(http://github.com/r'^say_hello/(?P%3Cname%3E[\w_-]+)/$', HelloView.as_view(), name='say_hello'), +) +``` + +No caso do exemplo acima o template renderizado pela ``HelloView`` teria +em seu contexto a variável ``name``. + +### Redirect View + +Redireciona o usuário para a url informada. + +A URL a ser redirecionada pode conter parâmetros no estilo dicionário-de-strings. +Os parâmetros capturados na URL do ``RedirectView`` serão repassados para a +URL que o usuário está sendo redirecionado. + +O fluxo básico de execução dessa classe quando recebe +uma requisição é: + +1. ``dispatch()`` +2. ``http_method_not_allowed()`` +3. ``get_redirect_url()`` + +Considere a seguinte configuração de URL's para o exemplo de implementação: + +```python + +from django.conf.urls import url +from django.views.generic.base import RedirectView + +from article.views import ArticleCounterRedirectView, ArticleDetail + +urlpatterns = [ + url(http://github.com/r'^counter/(?P%3Cpk%3E[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'), + url(http://github.com/r'^details/(?P%3Cpk%3E[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'), +] +``` + +Exemplo de implementação: + +```python + +from django.shortcuts import get_object_or_404 +from django.views.generic.base import RedirectView + +from articles.models import Article + +class ArticleCounterRedirectView(RedirectView): + + permanent = False + query_string = True + pattern_name = 'article-detail' + + def get_redirect_url(http://github.com/self,%20*args,%20**kwargs): + article = get_object_or_404(Article, pk=kwargs['pk']) + article.update_counter() + return super(ArticleCounterRedirectView, self).get_redirect_url(http://github.com/*args,%20**kwargs) + +``` + +Principais atributos: + +- ``url``: A URL destino no formato de String +- ``pattern_name``: O nome do padrão de URL. Um ``reverse`` será aplicado + usando os mesmos + ``args`` e ``kwargs`` passados para a ``RedirectView`` +- ``permanent``: Se for ``True`` retorna o status code como 301, caso contrário, + retorna 302. +- ``query_string``: Se for ``True`` a query_string será enviada para a URL de + destino. + +## Display Views + +As duas views abaixo foram desenvolvidas para exibir informações. Tipicamente +essas views são as mais usadas na maioria dos projetos. + +### DetailView + +Renderiza um template contendo em seu contexto **um objeto** obtido pelo +parâmetro enviado na URL. + +No fluxo de execução dessa view o objeto que está sendo utilizado está em +``self.object`` + +O fluxo básico de execução dessa classe quando recebe +uma requisição é: + +1. ``dispatch()`` +2. ``http_method_not_allowed()`` +3. ``get_template_names()`` +4. ``get_slug_field()`` +5. ``get_queryset()`` +6. ``get_object()`` +7. ``get_context_object_name()`` +8. ``get_context_data()`` +9. ``get()`` +10. ``render_to_response()`` + +O fluxo parece grande e complexo mas na verdade é muito simples e facilmente +customizável. Basicamente o que acontece é: + +``get_template_names()`` retorna uma lista de templates que devem ser usados para +renderizar a resposta. Caso o primeiro template da lista não seja encontrado o +Django tenta o segundo e assim por diante. + +Em seguida o ``get_slug_field()`` entra em ação, essa função deve retornar o +nome do campo que será usado para fazer a busca pelo objeto. Por default o +Django procura pelo campo ``slug``. + +Agora o ``get_queryset`` deve retornar um queryset que será usado para buscar +um objeto. Aqui é um ótimo lugar para, por exemplo, aplicar um filtro para +exibir somente o Artigo cujo autor é o usuário logado. Considere o exemplo +abaixo: + +```python + +def ArtigoView(DetailView): + model = Artigo + + get_queryset(self): + return self.model.filter(user=request.user) + + # ... o restante do código foi suprimido +``` + +**IMPORTANTE:** O ``get_queryset()`` é chamado pela implementação default do +método ``get_object()``, se o ``get_object()`` for sobrescrito a chamada ao +``get_queryset()`` pode não ser realizada. + +O ``get_object()`` então é o responsável por retornar o objeto que será enviado +para o template. Normalmente essa função não precisa ser sobrescrita. + +Depois de obter o objeto que será enviado para o template é necessário saber +qual será o nome desse objeto no contexto do template, isso é feito pela função +``get_context_object_name()``, por default o nome do objeto no template será o +nome do ``Model``, no exemplo acima seria ``artigo`` + +Depois disso temos o ``get_context_data()`` que já foi comentado acima e então +o ``get()`` que obtém o objeto e coloca no contexto, e em seguida o +``render_to_response`` que renderiza o template. + +**IMPORTANTE:** É importante notar que o Django oferece variáveis de +instância para facilitar a customização do comportamento da classe. +Por exemplo a troca do nome do objeto pode ser feita alterando a variável de +instância ``context_object_name`` ao invés de sobrescrever a função +``get_object_name()``. + +Abaixo segue um exemplo, onde exibir os detalhes de um *Artigo* somente se o +usuário for o autor dele e vamos pegar esse *Artigo* pelo campo ``titulo`` e +renderizar esse artigo no template ``detalhe_artigo.html`` com o nome +``meu_artigo``. + +**views.py** +```python + +from django.views.generic.detail import DetailView +from django.utils import timezone + +from articles.models import Article + +class ArticleDetailView(DetailView): + slug_field = 'titulo' + model = Article + context_object_name = 'meu_artigo' + template_name = 'detalhe_artigo.html' + + get_queryset(self): + return self.model.filter(user=self.request.user) + +``` + +**urls.py** +```python +from django.conf.urls import url + +from article.views import ArticleDetailView + +urlpatterns = [ + url(http://github.com/r'^(?P%3Ctitulo%3E[-\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), +] +``` + +**detalhe_artigo.html** +```html +

{{ meu_artigo.titulo }}

+

{{ meu_artigo.conteudo }}

+

Reporter: {{ meu_artigo.user.name }}

+

Published: {{ meu_artigo.data_publicacao|date }}

+``` + +### ListView + +Uma página que representa uma lista de objetos. +Enquanto essa view está executando a variável ``self.object_list`` vai conter +a lista de objetos que a view está utilizando. + +O fluxo básico de execução dessa classe quando recebe +uma requisição é: + +1. ``dispatch()`` +2. ``http_method_not_allowed()`` +3. ``get_template_names()`` +5. ``get_queryset()`` +6. ``get_object()`` +7. ``get_context_object_name()`` +8. ``get_context_data()`` +9. ``get()`` +10. ``render_to_response()`` + +Nada de novo aqui certo? Podemos exibir apenas uma lista de Artigos que estão +com ``status='publicado'`` + +```python + +from django.views.generic.list import ListView +from django.utils import timezone + +from articles.models import Artigo + +class ArticleListView(ListView): + + model = Artigo + + def get_queryset(self, **kwargs): + return Artigo.objects.filter(status='publicado') +``` + +Outra opção seria: + +```python + +from django.views.generic.list import ListView +from django.utils import timezone + +from articles.models import Artigo + +class ArticleListView(ListView): + + model = Artigo + queryset = Artigo.objects.filter(status='publicado') +``` + +**artigo_list.html** +```html +

Articles

+
    +{% for article in object_list %} +
  • {{ article.pub_date|date }} - {{ article.headline }}
  • +{% empty %} +
  • No articles yet.
  • +{% endfor %} +
+``` + +**DICA:** Normalmente sobrescrevemos as funções quando o retorno depende dos +parâmetros da requisição e utilizamos as variáveis de instância quando não há +essa dependência. + +O nome do template que é usado em ambas as views ``DetailView`` e ``ListView`` +é determinado da seguinte forma: + +* O valor da variável ``template_name`` na View (se definido) +* O valor do campo ``template_name_field`` na instância do objeto que a view + está usando. +* ``/.html`` + + +## Editing Views + +As views descritas abaixo contém o comportamento básico para edição de conteúdo. + +### FormView + +Uma view que mostra um formulário. Se houver erro, mostra o formulário novamente +contendo os erros de validação. Em caso de sucesso redireciona o usuário para +uma nova URL. + +**forms.py** +```python + +from django import forms + +class ContactForm(forms.Form): + name = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + def send_email(self): + # send email using the self.cleaned_data dictionary + pass +``` + +**views.py** +```python + +from myapp.forms import ContactForm +from django.views.generic.edit import FormView + +class ContactView(FormView): + template_name = 'contact.html' + form_class = ContactForm + success_url = '/thanks/' + + def form_valid(self, form): + # This method is called when valid form data has been POSTed. + # It should return an HttpResponse. + form.send_email() + return super(ContactView, self).form_valid(form) +``` + +**contact.html** +```html +
{% csrf_token %} + {{ form.as_p }} + +
+``` + +As funções mais importantes do ``FormView`` são: + +- ``form_valid()``: Chamada quando o formulário é validado com sucesso +- ``form_invalid()``: Chamada quando o formuĺário contém erros +- ``get_sucess_url()``: Chamada quando o formulário é validado com sucesso + e retorna a url para qual o usuário deve ser redirecionado. + +### Views para lidar com ``models`` (ModelForms) + +Grande parte do "poder" das CBV's vem quando precisamos trabalhar com models. + +As views listadas abaixo: ``CreateView``, ``UpdateView`` e ``DeleteView`` foram +criadas para facilitar esse trabalho com os models, essas views podem gerar um +``ModelForm`` de maneira automática, desde que seja possível determinar qual +é o model que a view está utilizando. + +A view vai tentar determinar o model a ser usado das seguintes formas: + +- Se houver um atributo ``model`` na classe +- Se o método ``get_object()`` retorna um objeto, a classe desse objeto será + usada +- Se houver um atributo ``queryset`` o model do ``queryset`` será utilizado + +Você não precisa nem mesmo definir um ``success_url`` as views ``CreateView`` e +``UpdateView`` utilizam automaticamente a função ``get_absolute_url()`` do model +se essa função existir. + +Você também pode customizar o formulário usado na view se você precisar de algum +tratamento adicional, para fazer isso basta definir a classe de formulários a ser +usada no atributo ``form_class``: + +```python + +from django.views.generic.edit import CreateView +from myapp.models import Author +from myapp.forms import AuthorForm + +class AuthorCreate(CreateView): + model = Author + form_class = AuthorForm +``` + +### CreateView, UpdateView e DeleteView + +Uma view que exibe um form para criar, atualizar ou apagar um objeto. +Caso existam erros no formulário, este é exibido novamente junto com as +mensagens de erro. + +Em caso de sucesso o objeto é salvo. + +**models.py** + +```python + +from django.core.urlresolvers import reverse +from django.db import models + +class Author(models.Model): + name = models.CharField(max_length=200) + + def get_absolute_url(http://github.com/self): + return reverse('author-detail', kwargs={'pk': self.pk}) +``` + +**views.py** +```python + +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.core.urlresolvers import reverse_lazy +from myapp.models import Author + +class AuthorCreate(CreateView): + model = Author + fields = ['name'] + +class AuthorUpdate(UpdateView): + model = Author + fields = ['name'] + +class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('author-list') + +``` + +**urls.py** +```python +from django.conf.urls import url +from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete + +urlpatterns = [ + # ... + url(http://github.com/r'author/add/),%20AuthorCreate.as_view(), name='author_add'), + url(http://github.com/r'author/(?P%3Cpk%3E[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'), + url(http://github.com/r'author/(?P%3Cpk%3E[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'), +] +``` + +O atributo ``fields`` determina quais campos do model devem estar presentes no +formulário. É obrigatório especificar o atributo ``fields`` ou então o atributo +``form_class``, nunca os dois ao mesmo tempo, pois isso geraria uma exceção +[``ImproperlyConfigured``](https://docs.djangoproject.com/en/1.8/ref/exceptions/#django.core.exceptions.ImproperlyConfigured). + +É importante notar também que a ``DeleteView`` exibe as informações do objeto que +será deletado quando é acessada usando o verbo ``GET``, quando usado o verbo +``POST`` o objeto é efetivamente apagado. + +**DICA**: O nome dos templates é determinado da seguinte forma: + +- ``CreateView`` e UpdateView usam myapp/author_form.html +- ``DeleteView`` usa myapp/author_confirm_delete.html + +## Date Views + +Date-based generic views são views com a função de exibir páginas com dados +filtrados por datas, por exemplo: posts em um blog, notícias, consultas ao médico, etc. + +### ArchiveIndexView + +Uma página que exibe os "últimas" objetos inseridos, desconsiderando aqueles com uma +data futura a não ser que o atributo ``allow_future`` seja definido como ``True``. + +É importante notar que: + +- O nome default do ``context_object_name`` é ``latest``. +- O sufixo ``_archive`` no nome do template. +- Além da lista de objetos o contexto também contem a variável ``date_list`` + contendo todos os anos que tem objetos em ordem decrescente. + Isso pode ser alterado para mês ou dia usando o atributo + ``date_list_period``. Isso se aplica a todas as *Data-based generic views*. + +Implementação simples: + +**urls.py** +```python + +from django.conf.urls import url +from django.views.generic.dates import ArchiveIndexView + +from myapp.models import Article + +urlpatterns = [ + url(r'^archive/$', + ArchiveIndexView.as_view(model=Article, date_field="pub_date"), + name="article_archive"), +] +``` + +### YearArchiveView + +Uma página para exibir um arquivo anual. Retorna todos os objetos de um +determinado ano. + +No contexto além da lista de objetos temos ainda: + +- ``date_list``: Um objeto QuerySet contendo todos os meses que tenham objetos + naquele ano representados como objetos datetime.datetime em ordem crescente. +- ``year``: Um objeto datetime.datetime representando o ano atual +- ``next_year``: Um objeto datetime.datetime representando o próximo ano +- ``previous_year``: Um objeto datetime.datetime representando o ano anterior + +Exemplo de implementação: + +**views.py** +```python +from django.views.generic.dates import YearArchiveView + +from myapp.models import Article + +class ArticleYearArchiveView(YearArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + allow_future = True +``` + +**urls.py** +```python +from django.conf.urls import url + +from myapp.views import ArticleYearArchiveView + +urlpatterns = [ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/$', + ArticleYearArchiveView.as_view(), + name="article_year_archive"), +] +``` + +**article_archive_year.html** +```html +
    + {% for date in date_list %} +
  • {{ date|date }}
  • + {% endfor %} +
+``` + +### MonthArchiveView + +Uma página para exibir um arquivo mensal. Retorna todos os objetos de um +determinado mês. + +No contexto além da lista de objetos temos ainda: + +- ``date_list``: Um objeto QuerySet contendo todos os dias que tenham objetos + naquele mês representados como objetos datetime.datetime em ordem crescente. +- ``month``: Um objeto datetime.datetime representando o mês atual +- ``next_month``: Um objeto datetime.datetime representando o próximo mês +- ``previous_month``: Um objeto datetime.datetime representando o mês anterior + +Exemplo de implementação: + +**views.py** +```python +from django.views.generic.dates import MonthArchiveView + +from myapp.models import Article + +class ArticleMonthArchiveView(MonthArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + allow_future = True +``` + +**urls.py** +```python +from django.conf.urls import url + +from myapp.views import ArticleMonthArchiveView + +urlpatterns = [ + # Example: /2012/aug/ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/$', + ArticleMonthArchiveView.as_view(), + name="archive_month"), + # Example: /2012/08/ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[0-9]+)/$', + ArticleMonthArchiveView.as_view(month_format='%m'), + name="archive_month_numeric"), +] +``` + +**article_archive_month.html** +```html +
    + {% for article in object_list %} +
  • {{ article.pub_date|date:"F j, Y" }}: {{ article.title }}
  • + {% endfor %} +
+ +

+ {% if previous_month %} + Previous Month: {{ previous_month|date:"F Y" }} + {% endif %} + {% if next_month %} + Next Month: {{ next_month|date:"F Y" }} + {% endif %} +

+``` + +### WeekArchiveView + +Uma página para exibir um arquivo semanal. Retorna todos os objetos de uma +determinada semana. + +No contexto além da lista de objetos temos ainda: + +- ``week``: Um objeto datetime.datetime representando a semana atual +- ``next_week``: Um objeto datetime.datetime representando a próxima semana +- ``previous_week``: Um objeto datetime.datetime representando a semana anterior + +Implementação simples: + +**views.py** +```python +from django.views.generic.dates import WeekArchiveView + +from myapp.models import Article + +class ArticleWeekArchiveView(WeekArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + week_format = "%W" + allow_future = True +``` + +**urls.py** +```python +from django.conf.urls import url + +from myapp.views import ArticleWeekArchiveView + +urlpatterns = [ + # Example: /2012/week/23/ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/week/(?P[0-9]+)/$', + ArticleWeekArchiveView.as_view(), + name="archive_week"), +] +``` + +**article_archive_week.html** +```html +

Week {{ week|date:'W' }}

+ +
    + {% for article in object_list %} +
  • {{ article.pub_date|date:"F j, Y" }}: {{ article.title }}
  • + {% endfor %} +
+ +

+ {% if previous_week %} + Previous Week: {{ previous_week|date:"F Y" }} + {% endif %} + {% if previous_week and next_week %}--{% endif %} + {% if next_week %} + Next week: {{ next_week|date:"F Y" }} + {% endif %} +

+``` + +### DayArchiveView + +Uma página para exibir um arquivo diário. Retorna todos os objetos de um +determinado dia. + +No contexto além da lista de objetos temos ainda: + +- ``day``: Um objeto datetime.datetime representando o dia atual +- ``next_day``: Um objeto datetime.datetime representando o próximo dia +- ``previous_day``: Um objeto datetime.datetime representando o dia anterior +- ``next_month``: Um objeto datetime.datetime representando o primeiro dia do + próximo mês +- ``previous_month``: Um objeto datetime.datetime representando o primeiro dia + do mês anterior + +Implementação simples: + +**views.py** +```python +from django.views.generic.dates import DayArchiveView + +from myapp.models import Article + +class ArticleDayArchiveView(DayArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + allow_future = True +``` + +**urls.py** +```python +from django.conf.urls import url + +from myapp.views import ArticleDayArchiveView + +urlpatterns = [ + # Example: /2012/nov/10/ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/$', + ArticleDayArchiveView.as_view(), + name="archive_day"), +] +``` + +**article_archive_day.html** +```html +

{{ day }}

+ +
    + {% for article in object_list %} +
  • {{ article.pub_date|date:"F j, Y" }}: {{ article.title }}
  • + {% endfor %} +
+ +

+ {% if previous_day %} + Previous Day: {{ previous_day }} + {% endif %} + {% if previous_day and next_day %}--{% endif %} + {% if next_day %} + Next Day: {{ next_day }} + {% endif %} +

+``` + +### TodayArchiveView + +É a mesma coisa do ``DayArchiveView`` mas não usa os parâmetros da URL para +determinar o ano/mês/dia. + +O que muda é o urls.py, veja o exemplo abaixo: + +```python +from django.conf.urls import url + +from myapp.views import ArticleTodayArchiveView + +urlpatterns = [ + url(r'^today/$', + ArticleTodayArchiveView.as_view(), + name="archive_today"), +] +``` + +### DateDetailView + +É a mesma coisa que a ``DetailView`` com a diferença que a data é utilizada +junto com o pk/slug para determinar qual objeto deve ser obtido. + +O que muda é o urls.py, veja o exemplo abaixo: + +```python +from django.conf.urls import url +from django.views.generic.dates import DateDetailView + +urlpatterns = [ + url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/(?P[0-9]+)/$', + DateDetailView.as_view(model=Article, date_field="pub_date"), + name="archive_date_detail"), +] +``` + +### Conclusão + +Longe de tentar exaurir um assunto de tamanha complexidade e abrangência minha +intenção com esse artigo foi mostrar o funcionamento básico das Class Based Views +e quem sabe incentivar você a utilizar CBV's no seu próximo projeto. + +Envie para mim qualquer dúvida, crítica ou sugestão que você tiver em qualquer +uma das minhas redes sociais, posso demorar um pouco a responder mas eu respondo! :) + +Ah, se você se interessou pelo assunto e quer se aprofundar mais eu aconselho +começar pela [Documentação oficial](https://docs.djangoproject.com/en/1.8/topics/class-based-views/intro/) + +### Referências + +- +- +- +- \ No newline at end of file diff --git a/content/como-distribuir-seu-projeto-python-com-pypi.md b/content/como-distribuir-seu-projeto-python-com-pypi.md new file mode 100644 index 000000000..d3e8ac050 --- /dev/null +++ b/content/como-distribuir-seu-projeto-python-com-pypi.md @@ -0,0 +1,222 @@ +Title: Como distribuir sua aplicação Python com PyPI +Slug: como-distribuir-sua-aplicacao-python-com-pypi +Date: 2016-06-17 13:47:24 +Category: Python +Tags: python, pypi, tutorial, desenvolvimento, pypi, pip +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +Imagine a seguinte situação: você passou alguns dias (ou mesmo meses) desenvolvendo uma módulo python, escreveu testes, implementou funcionalidades e depois de alguns ajustes, chegou a hora de liberar seu software para que outros desenvolvedores possam utilizá-lo. Qual o melhor modo de distribuí-lo? + +Caro leitor, se você costuma programar em Python (seja profissionalmente ou não) provavelmente já instalou outros módulos usando o [PyPI](https://pypi.python.org/pypi), através do comando abaixo: + +```bash +pip install nomedomodulo +``` + +Não seria interessante usar o mesmo método para distribuir a sua aplicação? Sim? Então mãos a obra. + +### Sobre o PyPI - Python Package Index + +O site [PyPI](https://pypi.python.org/pypi), é um repositório de *softwares* desenvolvidos na linguagem Python. Em outras palavras, ele garante que seu pacote Python sempre esteja disponível para a instalação. O seu funcionamente é simples, porém algumas configurações inicias devem ser feitas para que tudo funcione corretamente. + +### Crie uma conta + +Primeiramente, para distribuir seus pacotes usando o [PyPI](https://pypi.python.org/pypi), precisamos criar uma conta em ambos os sites: + +* [PyPI Live](https://pypi.python.org/pypi?%3Aaction=register_form) +* [PyPI Test](https://testpypi.python.org/pypi?%3Aaction=register_form) + +Recomendo que você utilize o mesmo email e senha para ambos os sites. Posteriormente, isso tornará mais fácil o processo de configuração. + +### Configurando o ambiente + +O próximo passo é criar um arquivo `.pypirc` em sua `home`. Esse arquivo contem informações de auteticação, tanto para o [PyPI Live](https://pypi.python.org/pypi) quando para o [PyPI Test](https://testpypi.python.org/pypi). + +```bash +touch ~/.pypirc +``` + +Apesar de não ser obrigatório a criação desse aquivo, ele facilita muito nosso trabalho, uma vez que você não precisaremos inserir nosso email e senha toda vez que formos enviar nosso código para o [PyPI Live](https://pypi.python.org/pypi). + +Abra o arquivo `.pypirc` em seu editor de texto favorito, e insira as informações abaixo. + +```bash +[distutils] +index-servers = + pypi + pypitest + +[pypi] +repository=https://pypi.python.org/pypi +username=seu_nomedeusuario +password=sua_senha + +[pypitest] +repository=https://testpypi.python.org/pypi +username=seu_nomedeusuario +password=sua_senha + +``` +Em *username* insira seu nome de usuário e *password*, insira sua senha. Faça isso tanto para o `pypi` quanto para o `pypitest`. + +Um observação importante é que, caso a sua senha possua espaço, não a coloque entre aspas. Por exemplo, se a sua senha for "batuque da viola doida", coloque exatamente o mesmo texto em *password*. + + +```bash +password=batuque da viola doida +``` + +### Preparando o seu módulo Python + +Todo pacote distribuído pelo [PyPI](https://pypi.python.org/pypi) precisa ter uma arquivo `setup.py` em seu diretório raiz. E se seu projeto também usa um arquivo *readme* em *markdown* (normalmente chamado `README.md`) você também precisará criar um arquivo chamado `setup.cfg`no diretório raiz do módulo. + +Como exemplo, iremos utilizar o módulo [codigo_avulso_test_tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial) que criei para ser utilizado como exemplo em nossos tutoriais. Assim, temos a seguinte estrutura básica de diretórios: + +```bash +. +├── codigo_avulso_test_tutorial +│   ├── circulo.py +│   ├── figura_geometrica.py +│   ├── __init__.py +│   └── quadrado.py +├── LICENSE +├── README.md +├── setup.cfg +├── setup.py +└── test + ├── circulo_test.py + ├── figura_geometrica_test.py + ├── __init__.py + └── quadrado_test.py + +``` +Aqui, o que nos interessa são os arquivos `setup.py` e `setup.cfg`. Dentro do arquivo `setup.py` temos várias informações sobre nossa aplicação que serão usadas pelo [PyPI](https://pypi.python.org/pypi). + +```python +# -*- coding: utf-8 -*- +from setuptools import setup + +setup( + name='codigo-avulso-test-tutorial', + version='0.1.1', + url='https://github.com/mstuttgart/codigo-avulso-test-tutorial', + license='MIT License', + author='Michell Stuttgart', + author_email='michellstut@gmail.com', + keywords='tutorial test unittest codigoavulso', + description=u'Tutorial de teste unitário em Python para o blog Código Avulso', + packages=['codigo_avulso_test_tutorial'], + install_requires=[], +) +``` +O nome de cada *tag* é autoexplicativo, então não vou entrar em detalhes. Basta você usar o código acima e substituir com os dados do seu pacote. + +O próximo passo é adicionar o seguinte conteúdo no arquivo `setup.cfg` (caso você o tenha criado). + +```bash +[metadata] +description-file = README.md +``` +Esse arquivo irá dizer ao [PyPI](https://pypi.python.org/pypi) onde seu arquivo *readme* está. + +### Publicando sua aplicação Python + +Agora iremos estudar os passos para enviar nossa aplicação para [PyPI](https://pypi.python.org/pypi), para que ela fique disponível para ser instalada através do `pip`. + +#### Enviando para PyPI Test + +Primeiramente, vamos registrar nossa aplicação no [PyPI Test](https://testpypi.python.org/pypi). Esse passo serve para verificarmos se está tudo certo com nosso pacote e também validar se já não existe outro módulo com o mesmo nome. +Registramos nossa aplicação com o seguinte comando: + +```bash +python setup.py register -r pypitest +``` + +Se tudo ocorrer bem teremos a seguinte saída (Server responde 200): + +```bash +running register +running egg_info +creating codigo_avulso_test_tutorial.egg-info +writing codigo_avulso_test_tutorial.egg-info/PKG-INFO +writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt +writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +running check +Registering codigo-avulso-test-tutorial to https://testpypi.python.org/pypi +Server response (200): OK +``` +Caso exista outro pacote com o mesmo nome, teríamos de escolher outro nome para o nosso pacote. Agora com nosso pacote devidamente registrado, executamos o comando abaixo para que o pacote seja enviado para o [PyPI Test](https://testpypi.python.org/pypi). + +```bash +python setup.py sdist upload -r pypitest +``` + +Se tudo ocorrer bem (Server responde 200), você verá uma saída semelhante a esta e já poderá ver sua aplicação na lista do [PyPI Test](https://testpypi.python.org/pypi). + +```bash +running sdist +running egg_info +writing codigo_avulso_test_tutorial.egg-info/PKG-INFO +writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt +writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt +reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +warning: sdist: standard file not found: should have one of README, README.rst, README.txt + +. +. +. + +creating dist +Creating tar archive +removing 'codigo-avulso-test-tutorial-0.1.1' (and everything under it) +running upload +Submitting dist/codigo-avulso-test-tutorial-0.1.1.tar.gz to https://testpypi.python.org/pypi +Server response (200): OK + +``` + +#### Enviando para PyPI Live + +Agora é pra valer. Executamos o mesmo passos para o [PyPI Test](https://testpypi.python.org/pypi). + +```bash +python setup.py register -r pypi +``` + +Tudo ocorrendo bem, enviamos nosso pacote: + +```bash +python setup.py sdist upload -r pypi +``` + +Parabéns! Com esse ultimo passo, publicamos o nosso pacote Python com sucesso! Agora ele pode ser [visualizado na lista de aplicações](https://pypi.python.org/pypi/codigo-avulso-test-tutorial/0.1.1) do [PyPI](https://pypi.python.org/pypi) e ser instalado usando `pip`. + +```bash +pip install nomedopacote +``` + +Ou, para o nosso exemplo: + +```bash +pip install codigo_avulso_test_tutorial +``` + +### Conclusão + +É isso pessoal. Neste tutorial vimos como distribuir nossa aplicação Python, desde a crição na conta no [PyPI](https://pypi.python.org/pypi) até o registro e *upload* da nossa aplicação. Espero que tenham gostado e caso tenham alguma dúvida, deixem um comentário. + +Obrigado pela leitura e até o próximo tutorial. + +### Referências + +* [Documentação oficial](https://wiki.python.org/moin/CheeseShopTutorial#Submitting_Packages_to_the_Package_Index) +* [How to Host your Python Package on PyPI with GitHub](https://www.codementor.io/python/tutorial/host-your-python-package-using-github-on-pypi) +* [How to submit a package to PyPI](http://peterdowns.com/posts/first-time-with-pypi.html) diff --git a/content/como-encontrar-solucoes-python.md b/content/como-encontrar-solucoes-python.md new file mode 100644 index 000000000..0c3fbe70d --- /dev/null +++ b/content/como-encontrar-solucoes-python.md @@ -0,0 +1,221 @@ +Title: Como encontrar soluções para seus problemas com Python +Slug: como-encontrar-solucoes-python +Date: 2016-01-09 10:30 +Tags: python,blog,tutorial,aulas +Author: Eric Hideki +Email: eric8197@gmail.com +Github: erichideki +Bitbucket: erichideki +Site: http://ericstk.wordpress.com +Twitter: erichideki +Category: Python + +Como encontrar soluções para seus problemas com Python +---------------------------------------------------- + +Quando estamos aprendendo algo, o início geralmente é difícil. Conseguir absorver novos conceitos e entender como as coisas funcionam não é uma das tarefas mais simples, porém nessas horas precisamos lembrar do conceito de 'babysteps' (Um passo de cada vez), ter paciência e persistir. + +A diferença entre uma pessoa experiente comparado a um iniciante é que a pessoa experiente errou muito mais vezes do que um iniciante, e com o tempo aprendeu com os erros. + +E na área de programação temos inúmeros tutoriais e cursos espalhados pela internet, e geralmente as pessoas começam a aprender por conta própria. E quando acontece um problema (e isso irá acontecer inevitavelmente) e não sabemos como solucioná-lo, se torna um obstáculo chato. + +Neste artigo irei mostrar algumas formas de poder encontrar soluções para seus problemas enquanto aprende a programar. + +Os exemplos se aplicam a Python, mas podem ser exemplificados para qualquer outra linguagem ou tecnologia (se você trabalha com outras tecnologias como PHP ou Ruby, deixe nos comentários quais ferramentas utilizam para encontrar soluções dos seus problemas). + +**Dica:** Veja essa palestra fantástica do [Josh Kaufman sobre as primeiras 20 horas de aprendizado](http://tedxtalks.ted.com/video/The-First-20-Hours-How-to-Learn) + +## Python console + +Uma grande feature do Python é seu console interativo, por ele conseguimos testar nossos códigos e alguns scripts em tempo real. Vejamos um exemplo: + +```python +Python 3.4.3 (default, Oct 14 2015, 20:28:29) +[GCC 4.8.4] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> a = {'Eric', 'Python', 'JavaScript'} +>>> b = ('Django', 'Flask') +>>> a + b +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'set' and 'tuple' +>>> tuple(a) + b +('JavaScript', 'Eric', 'Python', 'Django', 'Flask') +``` + +### O que aconteceu? + +Na variável **a** criamos um *set* com os nomes *Eric, Python e JavaScript*, e depois criamos uma *tupla* com os nomes *Django e Flask*. + +Ao tentarmos juntar **a + b**, o interpretador Python nos retorna um erro: *TypeError: unsupported operand type(s) for +: 'set' and 'tuple'*. Ou seja, o que ele diz é que não podemos somar um set a uma tupla. + +O interpretador Python realiza as operações em tempo real, e se caso o que você deseja fazer não estiver correto, o interpretador irá informar o erro. Se o erro não for explícito para você, basta copiar e colar o erro no Google e encontrará os motivos do erro. + +Para resolver esse problema, uma das soluções apresentada é transformar nosso set em uma tupla, onde fazemos a conversão em tempo de execução com o comando **tuple(a) + b**. + +## [Ipython](http://ipython.org/) + +Que tal termos um interpretador Python mais poderoso e com mais funcionalidades que o tradicional? O [Ipython](http://ipython.org/) foi criado especificamente esse objetivo. + +Vamos explorá-lo um pouco: + + +```python +$ipython + +Python 2.7.6 (default, Jun 22 2015, 17:58:13) +Type "copyright", "credits" or "license" for more information. + +IPython 1.2.1 -- An enhanced Interactive Python. +? -> Introduction and overview of IPython's features. +%quickref -> Quick reference. +help -> Python's own help system. +object? -> Details about 'object', use 'object??' for extra details. + +In [1]: nome = "Eric" + +In [2]: nome. +nome.capitalize nome.isalnum nome.lstrip nome.splitlines +nome.center nome.isalpha nome.partition nome.startswith +nome.count nome.isdigit nome.replace nome.strip +nome.decode nome.islower nome.rfind nome.swapcase +nome.encode nome.isspace nome.rindex nome.title +nome.endswith nome.istitle nome.rjust nome.translate +nome.expandtabs nome.isupper nome.rpartition nome.upper +nome.find nome.join nome.rsplit nome.zfill +nome.format nome.ljust nome.rstrip +nome.index nome.lower nome.split + +In [2]: nome.startswith("O") +Out[2]: False + +In [3]: nome.startswith("E") +Out[3]: True + +In [4]: len(nome) +Out[4]: 4 + +In [5]: nome.lower() +Out[5]: 'eric' + +``` + +### O que aconteceu? + +Criamos uma variável **nome** onde é um simples String "Eric". E se digitarmos **nome.** e apertar TAB, o Ipython irá apresentar diversas operações que podemos fazer com essa variável. + +Por exemplo, **nome.startswith** verifica se a primeira letra ou número começa com o parâmetro passado a ele. + +Na primeira tentativa verificamos se a variável começa com a letra *O*, e ele me retornou **False**, ou seja, não é verdade. + +Na segunda tentativa tentamos com a letra **E**, e ele me retornou **True**, o que é verdade já que o nome **Eric** começa com a letra "E". + +O Ipython vai muito mais além do que isso, vá e dê uma olhada na página oficial e o que ele pode fazer. + +E temos também o [Ipython Notebook](http://ipython.org/notebook.html) que é FANTÁSTICO, amplamente utilizado para computação científica. + +## [Dreampie](http://www.dreampie.org/) + +Quando queremos testar algo mais elaborado, ter um console que permita criamos nossos códigos de forma mais organizada além do Interpretador padrão do Python e o IPython, podemos utilizar o [Dreampie](http://www.dreampie.org/) que é ideal para isso. + +Vamos a outro exemplo: + +```python +Python 2.7.6 (default, Jun 22 2015, 17:58:13) +[GCC 4.8.2] on linux2 +Type "copyright", "credits" or "license()" for more information. +DreamPie 1.1.1 +>>> colunas = [ +... {'pnome': 'Eric', 'unome': 'Hideki', 'id': 4}, +... {'pnome': 'Luciano', 'unome': 'Ramalho', 'id': 2}, +... {'pnome': 'David', 'unome': 'Beazley', 'id': 8}, +... {'pnome': 'Tim', 'unome': 'Peters', 'id': 1}, +... ] +>>> from operator import itemgetter +>>> colunas_por_nome = sorted(colunas, key=itemgetter('pnome')) +>>> colunas_por_id = sorted(colunas, key=itemgetter('id')) +>>> from pprint import pprint +>>> pprint(colunas_por_nome) +[{'id': 8, 'pnome': 'David', 'unome': 'Beazley'}, + {'id': 4, 'pnome': 'Eric', 'unome': 'Hideki'}, + {'id': 2, 'pnome': 'Luciano', 'unome': 'Ramalho'}, + {'id': 1, 'pnome': 'Tim', 'unome': 'Peters'}] +>>> pprint(colunas_por_id) +[{'id': 1, 'pnome': 'Tim', 'unome': 'Peters'}, + {'id': 2, 'pnome': 'Luciano', 'unome': 'Ramalho'}, + {'id': 4, 'pnome': 'Eric', 'unome': 'Hideki'}, + {'id': 8, 'pnome': 'David', 'unome': 'Beazley'}] +``` + +Preview da tela do Dreampie. + +### O que aconteceu? + +Temos uma lista de dicionários desordenado e queremos ordenar de acordo com os parâmetros que queremos. Com isso, utilizamos duas bibliotecas que já estão por padrão com o Python: *[operator](https://docs.python.org/2/library/operator.html?highlight=itemgetter#operator.itemgetter) e [pprint](https://docs.python.org/2/library/pprint.html)*. + +Dentro da biblioteca **operator** temos a funcionalidade **itemgetter**, onde através dos parâmetros que passamos, ele irá fazer a seleção. Já o **pprint** irá mostrar o resultado da nossa seleção de forma mais bonita. Vamos explicar detalhadamente o que cada coisa faz. + +Criamos nossa lista de dicionários: + +```python +>>> colunas = [ +... {'pnome': 'Eric', 'unome': 'Hideki', 'id': 4}, +... {'pnome': 'Luciano', 'unome': 'Ramalho', 'id': 2}, +... {'pnome': 'David', 'unome': 'Beazley', 'id': 8}, +... {'pnome': 'Tim', 'unome': 'Peters', 'id': 1}, +... ] +``` + +Importamos a biblioteca operator e sua funcionalidade itemgetter: + +```python +>>>from operator import itemgetter +``` + +E criamos nossa funcionalidade para ordenar a lista de dicionários de acordo com o parâmetro selecionado, que nesse caso será o primeiro nome, o **pname**: + +```python +>>> colunas_por_nome = sorted(colunas, key=itemgetter('pnome')) +``` + +Finalizando, utilizamos o **pprint** para exibir o resultado: + +```python +>>>pprint(colunas_por_nome) +``` + +Bacana, né? E também utilizamos a mesma lógica para ordenar de acordo com o **id**. + +Se quiser saber mais a respeito do Ipython, indico o artigo do [Python Help que fala a respeito](https://pythonhelp.wordpress.com/2011/02/22/dreampie-o-shell-python-que-voce-sempre-sonhou/). + +# [Python Tutor](http://www.pythontutor.com/) + +Ao observarmos uma funcionalidade queremos entender o que ele faz, e muitas vezes o que cada coisa no código faz não fica muito bem claro em nossas ideias. Por isso o Python Tutor existe! Ele exibe passo-a-passo o que está acontecendo no código. + + + +Clique em **Forward** e veja o que acontece. + +# Outras opções + +Também existem outras ferramentas que podem auxiliar e melhorar seu código: + +- **Anaconda para Sublime Text** - [http://damnwidget.github.io/anaconda/](http://damnwidget.github.io/anaconda/) +- **Autopep8** - [https://pypi.python.org/pypi/autopep8](https://pypi.python.org/pypi/autopep8) +- **Jedi** - [https://github.com/davidhalter/jedi](https://github.com/davidhalter/jedi) +- **Pyflakes** - [https://pypi.python.org/pypi/pyflakes](https://pypi.python.org/pypi/pyflakes) +- **PDB** - [https://docs.python.org/2/library/pdb.html](https://docs.python.org/2/library/pdb.html) + +## Locais onde podemos postar nossas dúvidas + +Vale sempre lembrar que é muito importante consultar a documentação oficial do Python, seja a [versão 2](https://docs.python.org/2/) ou a [versão 3](https://docs.python.org/3/). + +Também existem outro lugar muito legal, o **[Stackoverflow](http://pt.stackoverflow.com/)**. Se ainda o problema persistir, acesse as listas de discussões da comunidade Python no Brasil. + +- **Python Brasil** - [https://groups.google.com/forum/#!forum/python-brasil](https://groups.google.com/forum/#!forum/python-brasil) +- **Django Brasil** - [https://groups.google.com/forum/#!forum/django-brasil](https://groups.google.com/forum/#!forum/django-brasil) +- **Web2py Brasil** - [https://groups.google.com/forum/#!forum/web2py-users-brazil](https://groups.google.com/forum/#!forum/web2py-users-brazil) +- **Flask Brasil** - [https://groups.google.com/forum/#!forum/flask-brasil](https://groups.google.com/forum/#!forum/flask-brasil) +- **Comunidades locais da comunidade Python ao redor do Brasil** - [http://pythonbrasil.github.io/wiki/comunidades-locais](http://pythonbrasil.github.io/wiki/comunidades-locais) + +Deixe nos comentários seu feedback, e se tiver outra dica que não foi citado, não deixe de indicar. diff --git a/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md b/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md new file mode 100644 index 000000000..06ab38919 --- /dev/null +++ b/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md @@ -0,0 +1,269 @@ +Title: Configurando ambiente Django com Apache e mod_wsgi +Date: 2015-03-02 00:20 +Tags: python, django, apache, mod_wsgi, virtualenv, virtualenvwrapper +Category: Django +Slug: configurando-ambiente-django-com-apache-e-mod-wsgi +Author: Guilherme Louro +Email: guilherme-louro@hotmail.com +Github: guilouro +Twitter: guilhermelouro +Facebook: guilherme.louro.3 + + +### Entendendo a necessidade + +Muitas vezes encontramos dificuldade em colocar nossas aplicações para funcionar em um servidor devido ao pouco conhecimento em infraestrutura, principalmente aqueles que vieram do php, onde, subir um site e já o ver funcionando no ambiente final se trata apenas de subir os arquivos para a pasta **www** e pronto, certo? Não, não é bem por aí ... + +Normalmente quando configuramos a hospedagem de um domínio através de um software de gestão de alojamento web *([cpanel](http://cpanel.net) é o mais conhecido)* automaticamente o sistema configura o VirtualHost específico para o seu domínio cadastrado, ja direcionando a path para a sua pasta *www* ou **public_html**. Mas como isso é feito? Não entrarei em detalhes de como o cpanel funciona, mas irei demonstrar aqui como configuramos um servidor com [apache](http://httpd.apache.org/docs/) para receber nossa aplicação. + +### Mas por que o Apache? + +A partir do momento que eu mudei meu foco, saindo do PHP para trabalhar com **Python**, eu acabei "abandonando" o Apache para trabalhar com Nginx. Porém, me deparei com um projeto onde tinha que funcionar em uma Hospedagem compartilhada na qual só funciona o apache. Como não vi nada relacionado a essa configuração aqui no **Pythonclub**, achei que seria útil para muitos que podem cair em uma situação parecida com a minha, ou simplesmente prefira usar o Apache do que o Nginx. + +Caso o seu interesse seja mesmo usar o Nginx (pode parar por aqui), acho ótimo!!! Te dou todo apoio e ainda te indico um ótimo post para isso, do nosso amigo [Igor Santos](https://github.com/igr-santos). + +- [Configurando um servidor de produção para aplicações Python](http://pythonclub.com.br/configurando-um-servidor-de-producao-para-aplicacoes-python.html) (Nginx) + +Agora, chega de conversa e vamos ao que interessa. + +### Como fazer? + +Existem várias maneiras de se fazer o Django trabalhar com apache, uma delas é a combinação Apache + [mod_wsgi](http://code.google.com/p/modwsgi/) e será dessa forma que faremos. Com mod_wsgi podemos implementar qualquer aplicação Python que suporte a interface **Python WSGI**. + +##### Instalando alguns pacotes necessários + +Antes de mais nada, atualize a lista de pacotes + +```bash +$ sudo apt-get update +``` + +*Apache + mod_wsgi* + +```bash +$ sudo apt-get install apache2 libapache2-mod-wsgi +``` + +*Python setup tools + pip* + +```bash +$ sudo apt-get install python-setuptools +$ sudo apt-get install python-pip +``` + +### Vamos testar o WSGI? + +Vamos fazer um teste com uma aplicação simples em python. + +Começe criando um diretório na raiz do apache *(DocumentRoot)* + +```bash +$ sudo mkdir /var/www/wsgi_test +``` + +Em seguida vamos criar nossa app de teste ... + +```bash +$ sudo vim /var/www/app.wsgi +``` + +... e escrever nossa app python compatível com WSGI + +```python +def application(environ, start_response): + status = '200 OK' + output = 'Hello World!' + response_headers = [('Content-type', 'text/plain'), + ('Content-Length', str(len(output)))] + start_response(status, response_headers) + return [output] +``` + +Vamos criar agora um host para usar como nosso domínio da aplicação teste + +```bash +$ sudo vim /etc/hosts +``` + +Adicione esta linha ao seu arquivo hosts + +```bash +127.0.0.1 wsgi_test +``` + +E vamos configurar nosso VirtualHost no Apache. + +```bash +$ sudo vim /etc/apache2/sites-available/wsgi_test +``` + +```apache + + ServerName wsgi_test + DocumentRoot /var/www/wsgi_test + + Order allow,deny + Allow from all + + WSGIScriptAlias / /var/www/wsgi_test/app.wsgi + +``` + +Ative-o + +```bash +$ sudo a2ensite wsgi_test +``` + +*Obs: esse comando cria um link simbólico do wsgi_test para a pasta sites-enabled. Você pode fazer isso manualmente.* + +Reinicie o apache: + +```bash +$ sudo service apache2 reload +``` + +Feito isso abra o internet explorer seu navegador preferido e acesse [http://wsgi_test](http://wsgi_test). Se você está vendo a mensagem *"Hello World"* pode comemorar, o wsgi está funcionando com o apache. + +### Configurando Django com WSGI + +Até o momento entendemos como funciona a configuração do apache para receber uma aplicação Python com WSGI. Podemos usar essa ideia para qualquer aplicação python, porém veremos como fica essa configuração para trabalhar com **Django**. + +##### Criando o ambiente + +É sempre bom trabalharmos com ambientes virtuais em nossas aplicações python, para isso temos o [virtualenv](https://virtualenv.pypa.io/en/latest/). Eu, particularmente, prefiro usar o [VirtualenvWrapper](https://virtualenvwrapper.readthedocs.org/en/latest/), que separa os ambientes virtuais das aplicações. Caso você não conheça, indico o post do [Arruda](https://github.com/arruda/) que foi o que me guiou quando comecei a usar. [Usando VirtualEnvWrapper](http://www.arruda.blog.br/programacao/python/usando-virtualenvwrapper/) + +No meu caso usei o virtualenvwrapper e meu filesystem é o seguinte: + +```bash ++-- /home/guilouro +| +-- .virtualenvs #[Ambientes] +| +-- www #[Projetos] +``` + +O Virtualenvwrapper criará meus projetos dentro de **www** e os ambientes em **.virtualenvs**. Mas para que isso aconteça temos que adicionar algumas linhas em nosso `~/.bashrc` + +```bash +# adicione no final do arquivo ~/.bashrc +# ... +export PROJECT_HOME=~/www +export WORKON_HOME=~/.virtualenvs +source /usr/local/bin/virtualenvwrapper.sh +``` + +Atualize com o comando: + +```bash +source ~/.bashrc +``` + +##### Criando nosso projeto + +```bash +$ mkproject wsgi +``` + +Com as configurações anteriores o virtualenvwrapper já irá ativar o ambiente e levar você para a pasta do projeto. Mas para ativar é muito simples, basta usar: + +```bash +$ workon wsgi +``` + +Com o nosso ambiente virtual criado partiremos então para a criação do nosso projeto django. Utilizarei a versão mais atual até o momento, nesse caso 1.7 + +```bash +$ pip install django +``` + +Não entrarei em detalhes para a configuração inicial do django, portanto irei usar um template que eu criei para a inicialização dos meus projeto. Criamos então o projeto dessa forma: + +```bash +# startproject pelo template +$ django-admin.py startproject --template https://github.com/guilouro/django-boilerplate/archive/master.zip wsgitest . +# instala os pacotes +$ pip install -r requirements.txt +# faz a migração +$ python manage.py migrate +``` + +Você encontra esse template aqui -> [django-boilerplate](https://github.com/guilouro/django-boilerplate) + +##### Criando um site no apache para o projeto + +Primeiramente, vamos criar um domínio fictício para responder com o nosso projeto ao ser acessado. + +```bash +$ sudo vim /etc/hosts + +127.0.0.1 djangowsgi.com +``` + +Agora vamos configurar o apache: + +```bash +$ sudo vim /etc/apache2/sites-available/djangowsgi +``` + +```apache +WSGIDaemonProcess djangowsgi.com python-path=/home/guilouro/www/wsgi:/home/guilouro/.virtualenvs/wsgi/lib/python2.7/site-packages +WSGIProcessGroup djangowsgi.com + + + ServerName djangowsgi.com + WSGIScriptAlias / /home/guilouro/www/wsgi/wsgitest/wsgi.py + + + + Order allow,deny + Allow from all + + + + Alias /media/ /home/guilouro/www/wsgi/media/ + Alias /static/ /home/guilouro/www/wsgi/static/ + + + Order allow,deny + Allow from all + + + + Order allow,deny + Allow from all + + + +``` + +Reinicie novamente o apache: + +```bash +$ sudo service apache2 reload +``` + +Explicarei agora um pouco do que foi usado nessa configuração + +**WSGIScriptAlias:** é a url na qual você estará servindo sua aplicação (/ indica a url raiz), e a segunda parte é a localização de um "arquivo WSGI". + +### Modo daemon + + O modo *daemon* é o modo recomendado para a execução do mod_wsgi(em plataformas não-windows). Ele gera um processo independente que lida com as solicitações e pode ser executado como um usuário diferente do servidor web. Um dos pontos positivos dele é que a cada alteração em seu projeto você não precisa restartar o apache, basta executar um `touch` no seu arquivo `wsgi.py` + +##### Directivas para o daemon + +**WSGIDaemonProcess:** Foi atribuido a ele o nosso servername e utilizamos `python-path` por se tratar de um projeto que esta em ambiente virtual. Passamos então nossas paths nele. + +**WSGIProcessGroup:** Atribuímos o servername a ele + +#### Testando a aplicação + +Agora acesse [http://djangowsgi.com](http://djangowsgi.com) e corre para o abraço. + +Espero que tenha ficado claro. Qualquer dúvida ou problema deixe nos comentários e vamos juntos tentar resolver. + +--- +##### Referências: + +- modwsgiwiki - [https://code.google.com/p/modwsgi/wiki/](https://code.google.com/p/modwsgi/wiki/) +- Blogalizado - [http://www.blogalizado.com.br/deploy-de-aplicacao-django-no-apache-com-mod_wsgi/](http://www.blogalizado.com.br/deploy-de-aplicacao-django-no-apache-com-mod_wsgi/) +- Documentação oficial do django - [https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/modwsgi/](https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/modwsgi/) \ No newline at end of file diff --git a/content/configurando-python-3.5-openshift.md b/content/configurando-python-3.5-openshift.md new file mode 100644 index 000000000..12e9b17d9 --- /dev/null +++ b/content/configurando-python-3.5-openshift.md @@ -0,0 +1,171 @@ +Title: Configurando OpenShift com Python 3.5 + Flask + Gunicorn +Slug: configurando-python-3.5-openshift-flask-gunicorn +Date: 2017-04-23 20:37:39 +Category: Python +Tags: python,tutorial,install,cloud +Author: Horácio Dias +Email: horacio.dias92@gmail.com +Gravatarid:57db1afcce141efc81193425d4a5bbf0 +Github: nenodias +Linkedin: nenodias92 +Facebook: nenodias +Summary: Tutorial básico de como configurar o python 3.5 com o openshift + flask + gunicorn, utilizando o diy (Do It Yourself), carregando um cartridge customizado ... + +Configurando OpenShift com Python 3.5 + +### Introdução + +O [OpenShift](https://www.openshift.com/) é uma plataforma de PasS que possibilita aos desenvolvedores "subir" aplicações na nuvem de uma maneira simples e rápida. Ele funciona a partir de gears(engrenagens) que representam máquinas que irão rodar as aplicações. Dentro de cada gear é possível instalar serviços, os são chamados de "cartridges". + +Existem 3 planos: + ++ Online (gratuito, com três gears) ++ Enterprise (pago com suporte) ++ Origin (versão da comunidade e pode ser utilizado livremente) + +Um problema que me deparei ao utilizar o Openshift é que ele não possui um cartridge com Python3.5. Porém existe uma forma um pouco mais complicada de resolver esse problema. + +Após fazer seu cadastro no OpenShift e instalar o [client tools](https://developers.openshift.com/managing-your-applications/client-tools.html) que contém as ferramentas necessárias para configurar nossa aplicação. + +Após tudo isso vamos colocar a mão na massa, abra seu terminal e vamos lá. + +### Criando a aplicação + +``` shell +rhc app create https://raw.githubusercontent.com/Grief/openshift-cartridge-python-3.5/master/metadata/manifest.yml diy-0.1 +``` +Substituindo "``" pelo nome de sua aplicação. +O arquivo manifest.yml criado por Changaco(github) e "forkeado" por Grief(github) contém as configurações de um cartridge customizado que contém o python 3.5. + +Para os curiosos o conteúdo do arquivo +``` +--- +Name: python +Cartridge-Short-Name: PYTHON +Display-Name: Only Python +Description: 'An embedded cartridge that provides only python, nothing else.' +Version: '3.5.0' +Versions: ['3.5.0', '2.7.11'] +License: The Python License +License-Url: http://docs.python.org/3/license.html +Vendor: python.org +Cartridge-Version: 0.0.2 +Cartridge-Vendor: praisebetoscience +Categories: +- service +- python +- embedded +Website: https://github.com/praisebetoscience/openshift-cartridge-python-3.5 +Help-Topics: + Developer Center: https://www.openshift.com/developers +Provides: +- python +Publishes: +Subscribes: + set-env: + Type: ENV:* + Required: false + set-doc-url: + Type: STRING:urlpath + Required: false +Scaling: + Min: 1 + Max: -1 +Version-Overrides: + '2.7.11': + Display-Name: Python 2.7 + License: The Python License, version 2.7 + Provides: + - python-2.7 + - python + - python(version) = 2.7 + '3.5.0': + Display-Name: Python 3.5 + License: The Python License, version 3.5 + Provides: + - python-3.5 + - python + - python(version) = 3.5 +``` + +Após isso sua aplicação já estárá executando, caso deseje acessar o endereço da mesma deverá ser http://``-.rhcloud.com. +Você verá que a página do seu projeto não é nada mais do que o diy (Dot It Yourself), que é uma aplicação Ruby de exemplo que você pode alterar, e é o que vamos fazer. + +Se você acessar o diretório do seu projeto verá que existe um diretório ".openshift", dentro desse diretório existe um outro diretório chamado "action_hooks", e dentro desse diretório existem dois arquivos "start" e "stop". + ++ "``/.openshift/action_hooks/start" ++ "``/.openshift/action_hooks/stop" + +Os dois arquivos são respectivamente os comandos para "subir" e "pausar" sua aplicação. + +### Flask +Vamos criar um projeto de exemplo, bem simples, que apenas nos retorne a versão do python utilizada. +Primeiramente vamos criar nosso requirements.txt, com gunicorn e o flask. + +"requirements.txt" +``` +gunicorn +flask +``` + +Depois disso vamos criar o arquivo app.py que conterá nossa aplicação. + +"app.py" +``` +import sys +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def index(): + return sys.version + +``` + Após isso basta fazer o commit de suas alterações. + + ``` shell + git add . + git commit -am 'Minhas alterações' + ``` + +Após isso você verá que sua aplicação não está rodando, pois ainda não alteramos os arquivos "start" e "stop". + +### Configurando o Gunicorn no Start e Stop +O projeto diy do openshift nos deixa uma variável de ambiente $OPENSHIFT_DIY_IP com o IP da máquina, dessa forma podemos passar a variável e porta ao gunicorn. + +"start" +``` shell +#!/bin/bash +nohup $HOME/python/usr/bin/pip3 install -r $OPENSHIFT_REPO_DIR/requirements.txt +cd $OPENSHIFT_REPO_DIR +nohup $HOME/python/usr/bin/gunicorn app:app --bind=$OPENSHIFT_DIY_IP:8080 |& /usr/bin/logshifter -tag diy & +``` + +A primeira linha é o [Shebang](https://pt.wikipedia.org/wiki/Shebang), o que significa que esse arquivo será executado pelo bash. +Na segunda linha vemos [nohup](https://pt.wikipedia.org/wiki/Nohup), que executa os comandos em uma sessão separada, vemos logo apóes vemos o uma chamada ao pip para instalar nossas dependências. +Na terceira linha vemos o nohup, e depois o gunicorn inicializa nossa aplicação flask. + +Isso só funciona pois o cartridge customizado instala o python3.5 dentro da pasta home do servidor do openshift. + +"stop" +``` shell +#!/bin/bash +source $OPENSHIFT_CARTRIDGE_SDK_BASH + +# The logic to stop your application should be put in this script. +if [ -z "$(ps -ef | grep gunicorn | grep -v grep)" ] +then + client_result "Application is already stopped" +else + kill `ps -ef | grep gunicorn | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1 +fi +``` +Podemos ver que o comando ps procura algum processo do gunicorn, caso ele exista o kill será chamado. + +Após isso, é só fazer o commit das alterações e você verá sua aplicação rodando. + +Espero que o post ajude a quem quer subir alguma aplicação com python3.5 e só tem o heroku como opção. + +### Referências +- [Imaster](https://www.profissionaisti.com.br/2015/04/openshift-paas-de-verdade/) diff --git a/content/conteinerizando-suas-aplicacoes-django-com-docker-e-fig.md b/content/conteinerizando-suas-aplicacoes-django-com-docker-e-fig.md new file mode 100755 index 000000000..0b09f92d4 --- /dev/null +++ b/content/conteinerizando-suas-aplicacoes-django-com-docker-e-fig.md @@ -0,0 +1,116 @@ +Title: Conteinerizando suas aplicações django com docker e fig +Slug: conteinerizando-suas-aplicacoes-django-com-docker-e-fig +Date: 2015-01-25 13:00 +Tags: django, docker, fig +Author: Hudson Brendon +Email: contato.hudsonbrendon@gmail.com +Github: hudsonbrendon +Twitter: hudsonbrendon +Facebook: hudson.brendon +Category: Django + +![Docker](/images/hudsonbrendon/django-fig.png) + + +Se você como eu é um desenvolvedor incansável quando o assunto é automatizar ao máximo seu workflow de trabalho,este post foi feito para você. O [fig](http://www.fig.sh/) utilizando-se do docker, torna a criação de ambientes de desenvolvimento algo muito simples. + + +#Instalação + +A instalação do fig é bem simples, primeiro você terá que ter o docker instalado em sua máquina, caso não tenha, siga esse [tutorial](http://hudsonbrendon.com/docker-introducao-e-aplicacao.html) onde exemplifico a instalação do mesmo de forma bem simples. Com o docker pronto, é hora de instalar o fig, essa ferramenta é um pacote python, e a forma mais simples de instalá-la é através do pip, que é o gerenciador de pacotes do python, caso não o tenha instalado em sua máquina, acesse o [site oficial](https://pip.pypa.io/en/latest/installing.html) e veja a forma mais simples para você obtê-lo. Com tudo pronto, execute no terminal. + +```bash +$ pip install -U fig +``` + +#Utilizando o fig com django + +Com o docker e o fig devidamente instalados em sua máquina, é hora de integrar o django com essa maravilhosa ferramenta, para tanto, criaremos um diretório com um nome qualquer, aqui chamado de "app", e dentro do mesmo criaremos um arquivo chamado "Dockerfile", com o seguinte conteúdo. + +```bash +FROM python:2.7 +ENV PYTHONUNBUFFERED 1 +RUN mkdir /code +WORKDIR /code +ADD requirements.txt /code/ +RUN pip install -r requirements.txt +ADD . /code/ +``` +Em seguinda criaremos um arquivo chamado "requirements.txt", com os seguintes pacotes. + +```bash +Django +psycopg2 +``` +E por fim um arquivo, "fig.yml", com a configuração abaixo. + +```bash +db: + image: postgres +web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - .:/code + ports: + - "8000:8000" + links: + - db +``` +#Quem é quem no jogo do bicho + +Com os arquivos criados é hora de entender qual a função de cada um no workflow. + +* **Dockerfile** - É o arquivo que especifica como uma imagem no docker será criada, os pacotes que serão instalados, usuários que serão criados, portas que serão expostas, diretórios que serão compartilhados entre o host e um container, etc. Para mais informações [acesse](http://hudsonbrendon.com/docker-introducao-e-aplicacao.html). + +* **requirements.txt** - É um arquivo que guarda todas as dependências de um projeto python. + +* **fig.yml** - É o arquivo de configuração do fig, é composto por blocos e cada bloco corresponde a um container, podendo eles serem "linkados", o fig utilizará esse arquivo como base para criar os conteineres necessários, e executar tudo que foi especificado no mesmo. + +Com os arquivos finalizados, é hora de criar uma aplicação em django, para isso basta. + +```bash +$ fig run web django-admin.py startproject figexample . +``` +E o resultado será esse: + +```bash +$ ls +Dockerfile fig.yml figexample manage.py requirements.txt +``` +Com a aplicação em mãos, a primeira coisa que você deve fazer é abrir o arquivo settings.py de sua aplicação, e configurar o banco de dados da mesma. Para isso no arquivo figexample/settings.py basta especificar as configurações abaixo no banco de dados. + +```python +DATABASES = { +'default': { +'ENGINE': 'django.db.backends.postgresql_psycopg2', +'NAME': 'postgres', +'USER': 'postgres', +'HOST': 'db', +'PORT': 5432, + } +} +``` +Com o banco configurado é hora de subir sua aplicação, na pasta raiz do projeto use. + +```bash +$ fig up +``` +O fig se encarregará de criar todos os conteineres, linkalos, e startar sua aplicação na porta 8000, acesse seu [localhost:8000](http://localhost:8000/) e você verá sua aplicação em execução. + + +Para rodar os comandos do django, você pode fazer da seguinte forma. + +```bash +$ fig run +``` +Por exemplo. + +```bash +$ fig run web ./manage.py syncdb +``` +Lembrando que esse comando sempre será o padrão. + +#Conclusão + +Como você pode ver, o fig em conjunto com o docker torna seu workflow algo extremamente simples e eficaz. O melhor disso tudo, é que, para trabalhar com esse mesmo ambiente em uma nova máquina, você apenas precisará do fig e docker instalados, acessar a rais do projeto e executar um fig up, gerando com isso,uma comodidade jamais vista. diff --git a/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md b/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md new file mode 100644 index 000000000..b07c0ef53 --- /dev/null +++ b/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md @@ -0,0 +1,161 @@ +Title: Criando dicts a partir de outros dicts +Slug: crie_dict-a-partir-de-outros-dicts +Date: 2019-10-01 20:20:29 +Category: Python +Tags: Python, Dict +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io +Summary: Crie dicts a partir de outros dicts + +Neste tutorial, será abordado o processo de criação de um *dict* ou dicionário, a partir de um ou mais *dicts* em Python. + +Como já é de costume da linguagem, isso pode ser feito de várias maneiras diferentes. + +## Abordagem inicial + +Pra começar, vamos supor que temos os seguintes dicionários: + +```python +dict_1 = { + 'a': 1, + 'b': 2, +} + +dict_2 = { + 'b': 3, + 'c': 4, +} +``` + +Como exemplo, vamos criar um novo dicionário chamado **new_dict** com os valores de **dict_1** e **dict_2** logo acima. Uma abordagem bem conhecida é utilizar o método *update*. + +```python +new_dict = {} + +new_dcit.update(dict_1) +new_dcit.update(dict_2) +``` + +Assim, temos que **new_dict** será: + +```python +>> print(new_dict) +{ + 'a': 1, + 'b': 3, + 'c': 4, +} +``` + +Este método funciona bem, porém temos de chamar o método *update* para cada *dict* que desejamos mesclar em **new_dict**. Não seria interessante se fosse possível passar todos os *dicts* necessários já na inicialização de **new_dict**? + +### Novidades do Python 3 + +O Python 3 introduziu uma maneira bem interessante de se fazer isso, utilizando os operadores `**`. + +```python +new_dict = { + **dict_1, + **dict_2, +} + +``` + +Assim, de maneira semelhante ao exemplo anterior, temos que **new_dict** será : + +```python +>> print(new_dict['a']) +1 +>> print(new_dict['b']) +3 +>> print(new_dict['c']) +4 +``` + +## Cópia real de *dicts* + +Ao utilizamos o procedimento de inicialização acima, devemos tomar conseiderar alguns fatores. Apenas os valores do primeiro nível serão realmente duplicados no novo dicionário. Como exemplo, vamos alterar uma chave presente em ambos os *dicts* e verificar se as mesmas possuem o mesmo valor: + +```python +>> dict_1['a'] = 10 +>> new_dict['a'] = 11 +>> print(dict_1['a']) +10 +>> print(new_dict['a']) +11 +``` + +Porém isso muda quando um dos valores de **dict_1** for uma *list*, outro *dict* ou algum objeto complexo. Por exemplo: + +```python +dict_3 = { + 'a': 1, + 'b': 2, + 'c': { + 'd': 5, + } +} +``` + +e agora, vamos criar um novo *dict* a partir desse: + +```python +new_dict = { + **dict_3, +} + +``` + +Como no exemplo anterior, podemos imaginar que foi realizado uma cópia de todos os elementos de **dict_3**, porém isso não é totalmente verdade. O que realmente aconteceu é que foi feita uma cópia *superficial* dos valores de **dict_3**, ou seja, apenas os valores de *primeiro nível* foram duplicados. Observe o que acontece quando alteramos o valor do *dict* presente na chave **c**. + +```python +>> new_dict['c']['d'] = 11 +>> print(new_dict['c']['d']) +11 +>> print(dict_3['c']['d']) +11 +# valor anterior era 5 + +``` + +No caso da chave **c**, ela contem uma referência para outra estrutura de dados (um *dict*, no caso). Quando alteramos algum valor de **dict_3['c']**, isso reflete em todos os *dict* que foram inicializados com **dict_3**. Em outras palavras, deve-se ter cuidado ao inicializar um *dict* a partir de outros **dicts** quando os mesmos possuírem valores complexos, como *list*, *dict* ou outros objetos (os atributos deste objeto não serão duplicados). + +De modo a contornar este inconveniente, podemos utilizar o método *deepcopy* da *lib* nativa [copy](https://docs.python.org/2/library/copy.html). Agora, ao inicializarmos **new_dict**: + +```python +import copy + +dict_3 = { + 'a': 1, + 'b': 2, + 'c': { + 'd': 5, + } +} + +new_dict = copy.deepcopy(dict_3) +``` + +O método *deepcopy* realiza uma cópia recursiva de cada elemento de **dict_3**, resolvendo nosso problema. Veja mais um exemplo: + +```python +>> new_dict['c']['d'] = 11 +>> print(new_dict['c']['d']) +11 +>> print(dict_3['c']['d']) +5 +# valor não foi alterado +``` + +## Conclusão + +Este artigo tenta demonstrar de maneira simples a criação de *dicts*, utilizando os diversos recursos que a linguagem oferece bem como os prós e contras de cada abordagem. + +## Referências + +Para mais detalhes e outros exemplos, deem uma olhada neste *post* do forum da Python Brasil [aqui](https://groups.google.com/forum/#!topic/python-brasil/OhUqYQ32M7E). + +É isso pessoal. Obrigado por ler! diff --git a/content/criando-novos-comandos-no-django-admin.md b/content/criando-novos-comandos-no-django-admin.md new file mode 100644 index 000000000..1d491774d --- /dev/null +++ b/content/criando-novos-comandos-no-django-admin.md @@ -0,0 +1,440 @@ +title: Criando novos comandos no django-admin +Slug: criando-novos-comandos-no-django-admin +Date: 2015-12-03 22:00 +Tags: Python, Django +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Django + +Veja aqui como criar o seu próprio comando para ser usado com o django-admin ou manage.py do Django. + +O [django-admin ou manage.py][1] já tem um bocado de comandos interessantes, os mais utilizados são: + +* [startproject][4] - cria novos projetos. +* [startapp][5] - cria novas apps. +* [makemigrations][6] - cria novas migrações baseadas nas mudanças detectadas nos modelos Django. +* [migrate][7] - sincroniza o banco de dados com as novas migrações. +* [createsuperuser][8] - cria novos usuários. +* [test][9] - roda os testes da aplicação. +* [loaddata][14] - carrega dados iniciais a partir de um json, por exemplo, `python manage.py loaddata fixtures.json` +* [shell][15] - inicializa um interpretador Python interativo. +* [dbshell][18] - acessa o banco de dados através da linha de comando, ou seja, você pode executar comandos sql do banco, por exemplo, diretamente no terminal. +* [inspectdb][16] - retorna todos os modelos Django que geraram as tabelas do banco de dados. +* [runserver][17] - roda o servidor local do projeto Django. + +Mas de repente você precisa criar um comando personalizado conforme a sua necessidade. A palavra chave é `BaseCommand` ou [Writing custom django-admin commands][2]. + +## Começando do começo + +> Importante: estamos usando Django 1.8.12 e Python 3. + +### Criando o projeto + +Eu usei esta sequência de comandos para criar o projeto. + +```bash +# Criando djangoproject. +mkdir djangoproject +cd djangoproject + +# Criando virtualenv +virtualenv -p python3 .venv + +# Ativando o .venv. +source .venv/bin/activate +# Diminuindo o caminho do prompt (opcional) +PS1="(`basename \"$VIRTUAL_ENV\"`)\e[1;34m:/\W\033[00m$ " + +# Instalando o Django +pip install django==1.8.12 +pip freeze > requirements.txt + +# Criando o projeto myproject ... +django-admin.py startproject myproject . +cd myproject + +# Criando a app 'core' ... +python ../manage.py startapp core +cd .. + +# Editando settings.py" +sed -i "/django.contrib.staticfiles/a\ 'myproject.core'," myproject/settings.py +``` + +Pronto! Agora nós já temos um projetinho Django funcionando. Note que o nome da app é **core**. + +### Criando as pastas + +Para criarmos um novo comando precisamos das seguintes pastas: + + core + ├── management + │ ├── __init__.py + │ ├── commands + │ │ ├── __init__.py + │ │ ├── novocomando.py + +No nosso caso, teremos 3 novos comandos, então digite, estando na pasta `djangoproject` + +```bash +mkdir -p core/management/commands +touch core/management/__init__.py +touch core/management/commands/{__init__.py,hello.py,initdata.py,search.py} +``` + + +## Sintaxe do novo comando + +> Importante: estamos usando Django 1.8.12 e Python 3. + +O Django 1.8 usa o `argparse` como parser de argumentos do `command`, mais informações em [module-argparse][19]. + +```python +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option + +class Command(BaseCommand): + help = 'Texto de ajuda aqui.' + option_list = BaseCommand.option_list + ( + make_option('--awards', '-a', + action="http://github.com/store_true", + help='Ajuda da opção aqui.'), + ) + + def handle(self, **options): + self.stdout.write('Hello world.') + if options['awards']: + self.stdout.write('Awards') +``` + +Entendeu? Basicamente o `handle` é a função que executa o comando principal, no caso o `self.stdout.write('Hello world.')`, ou seja, se você digitar o comando a seguir ele imprime a mensagem na tela. + +```bash +$ python manage.py hello +Hello World +``` + +`--awards` é um argumento opcional, você também pode digitar `-a`. + +```bash +$ python manage.py hello -a +Hello World +Awards +``` + +`action="http://github.com/store_true"` significa que ele armazena um valor verdadeiro. + +**Obs**: A partir do Django 1.8 os comandos de argumentos opcionais são baseados em `**options`. + + +Veja uma outra forma de escrever + +```python +from django.core.management.base import BaseCommand, CommandError + +class Command(BaseCommand): + + def add_arguments(self, parser): + # Argumento nomeado (opcional) + parser.add_argument('--awards', '-a', + action='http://github.com/store_true', + help='Ajuda da opção aqui.') + + def handle(self, *args, **options): + self.stdout.write('Hello world.') + if options['awards']: + self.stdout.write('Awards') +``` + +A diferença é que aqui usamos `parser.add_argument` ao invés de `make_option`. + +### hello.py + +```python +from django.core.management.base import BaseCommand, CommandError +# minimalista +class Command(BaseCommand): + help = 'Print hello world' + + def handle(self, **options): + self.stdout.write('Hello World') +``` + +**Uso** + +```bash +$ python manage.py hello +``` + +### initdata.py + +**Objetivo**: Obter alguns filmes de uma api e salvar os dados no banco. + +**api**: [omdbapi.com][3] + +**models.py** + +```python +from django.db import models + +class Movie(models.Model): + title = models.CharField(u'título', max_length=100) + year = models.PositiveIntegerField('ano', null=True, blank=True) + released = models.CharField(u'lançamento', max_length=100, default='', blank=True) + director = models.CharField('diretor', max_length=100, default='', blank=True) + actors = models.CharField('atores', max_length=100, default='', blank=True) + poster = models.URLField('poster', null=True, blank=True) + imdbRating = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) + imdbID = models.CharField(max_length=50, default='', blank=True) + + class Meta: + ordering = ['title'] + verbose_name = 'filme' + verbose_name_plural = 'filmes' + + def __str__(self): + return self.title +``` + +Não se esqueça de fazer + +```bash +python manage.py makemigrations +python manage.py migrate +``` + +**admin.py** + +Vamos visualizar pelo admin. + +```python +from django.contrib import admin +from core.models import Movie + +admin.site.register(Movie) +``` + +Instale o `requests` + +```bash +pip install requests +``` + +**initdata.py** + +O código a seguir é longo, mas basicamente temos + +* `print_red(name)` função que imprime um texto em vermelho (opcional) +* `get_html(year)` função que lê os dados da api usando [requests][20], e depois escolhe um filme randomicamente a partir de 2 letras +* `get_movie(year)` se o dicionário conter `{'Response': 'True', ...}` então retorna um dicionário do filme localizado +* `save()` salva os dados no banco +* `handle(movies, year)` este é o comando principal. Busca os filmes várias vezes, conforme definido pela variável `movies`, e salva os n filmes. + + +```python +# -*- coding: utf-8 -*- # + +import random +import string +import requests +from django.core.management.base import BaseCommand, CommandError +from django.core.exceptions import ValidationError +from optparse import make_option +from core.models import Movie + + +class Command(BaseCommand): + help = """Faz o crawler numa api de filmes e retorna os dados. + Uso: python manage.py initdata + ou: python manage.py initdata -m 20 + ou: python manage.py initdata -m 20 -y 2015""" + option_list = BaseCommand.option_list + ( + make_option('--movies', '-m', + dest='movies', + default=10, + help='Define a quantidade de filmes a ser inserido.'), + make_option('--year', '-y', + dest='year', + action='http://github.com/store', + default=None, + help='Define o ano de lançamento do filme.'), + ) + + def print_red(self, name): + """imprime em vermelho""" + print("\033[91m {}\033[00m".format(name)) + + def get_html(self, year): + """ + Le os dados na api http://www.omdbapi.com/ de forma aleatoria + e escolhe um filme buscando por 2 letras + """ + + # Escolhe duas letras aleatoriamente + letters = ''.join(random.choice(string.ascii_lowercase) for _ in range(2)) + + # Se não for definido o ano, então escolhe um randomicamente + if year is None: + year = str(random.randint(1950, 2015)) + url = 'http://www.omdbapi.com/?t={letters}*&y={year}&plot=short&r=json'.format(letters=letters, year=str(year)) + return requests.get(url).json() + + def get_movie(self, year, **kwargs): + """ Retorna um dicionário do filme """ + + movie = self.get_html(year) + j = 1 # contador + + # Faz a validação de Response. Se a resposta for falsa, então busca outro filme. + while movie['Response'] == 'False' and j < 100: + movie = self.get_html(year) + self.print_red('Tentanto %d vezes\n' % j) + j += 1 + return movie + + def save(self, **kwargs): + """SALVA os dados""" + try: + Movie.objects.create(**kwargs) + except ValidationError as e: + self.print_red(e.messages) + self.print_red('O objeto não foi salvo.\n') + + def handle(self, movies, year, **options): + """ se "movies" não for nulo, transforma em inteiro """ + + self.verbosity = int(options.get('verbosity')) + + if movies is not None: + movies = int(movies) + + # busca os filmes n vezes, a partir da variavel "movies" + for i in range(movies): + # verifica as validações + m = self.get_movie(year) + if m['imdbRating'] == "N/A": + m['imdbRating'] = 0.0 + + # Transforma "year" em inteiro + if "–" in m['Year']: + m['Year'] = year + + data = { + "title": m['Title'], + "year": m['Year'], + "released": m['Released'], + "director": m['Director'], + "actors": m['Actors'], + "poster": m['Poster'], + "imdbRating": m['imdbRating'], + "imdbID": m['imdbID'], + } + + self.save(**data) + + if self.verbosity > 0: + self.stdout.write('\n {0} {1} {2}'.format(i + 1, data['year'], data['title'])) + if self.verbosity > 0: + self.stdout.write('\nForam salvos %d filmes' % movies) +``` + +**Uso** + +```bash +Usage: python manage.py initdata [options] + +Faz o crawler numa api de filmes e retorna os dados. + Uso: python manage.py initdata + ou: python manage.py initdata -m 20 + ou: python manage.py initdata -m 20 -y 2015 +``` + + +### search.py + +**Objetivo**: Localizar o filme pelo título ou ano de lançamento. + +```python +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from core.models import Movie + + +class Command(BaseCommand): + help = """Localiza um filme pelo título ou ano de lançamento. + Uso: python manage.py search -t 'Ted 2' + ou: python manage.py search -y 2015 + ou: python manage.py search -t 'a' -y 2015""" + + option_list = BaseCommand.option_list + ( + make_option('--title', '-t', + dest='title', + default=None, + help='Localiza um filme pelo título.'), + make_option('--year', '-y', + dest='year', + default=None, + help='Localiza um filme pelo ano de lançamento.'), + ) + + def handle(self, title=None, year=None, **options): + """ dicionário de filtros """ + self.verbosity = int(options.get('verbosity')) + + filters = { + 'title__istartswith': title, + 'year': year + } + + filter_by = {key: value for key, value in filters.items() if value is not None} + queryset = Movie.objects.filter(**filter_by) + + if self.verbosity > 0: + for movie in queryset: + self.stdout.write("{0} {1}".format(movie.year, movie.title)) + self.stdout.write('\n{0} filmes localizados.'.format(queryset.count())) +``` + +**Uso** + +```bash +Usage: python manage.py search [options] + +Localiza um filme pelo título ou ano de lançamento. + Uso: python manage.py search -t 'Ted 2' + ou: python manage.py search -y 2015 + ou: python manage.py search -t 'a' -y 2015 +``` + + +[Aqui][10] tem um exemplo legal que eu usei como ideia pra fazer este post. + +Mais algumas referências: + +[Writing custom django-admin commands][2] + +[Zachary Voase: Fixing Django Management Commands][11] + +[Adding Custom Commands to manage.py and django-admin.py by dave][12] + +[1]: https://docs.djangoproject.com/en/1.8/ref/django-admin/ +[2]: https://docs.djangoproject.com/en/1.8/howto/custom-management-commands/ +[3]: http://www.omdbapi.com/ +[4]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#startproject-projectname-destination +[5]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#startapp-app-label-destination +[6]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#makemigrations-app-label +[7]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#migrate-app-label-migrationname +[8]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#createsuperuser +[9]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#test-app-or-test-identifier +[10]: https://github.com/rhblind/django-gcharts/blob/master/demosite/management/commands/initdata.py +[11]: http://zacharyvoase.com/2009/12/09/django-boss/ +[12]: http://thingsilearned.com/2009/03/13/adding-custom-commands-to-managepy-and-django-adminpy/ +[14]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#loaddata-fixture-fixture +[15]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#shell +[16]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#inspectdb +[17]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#runserver-port-or-address-port +[18]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#dbshell +[19]: https://docs.python.org/2/library/argparse.html#module-argparse +[20]: http://docs.python-requests.org/en/latest/ \ No newline at end of file diff --git a/content/curso-asyncio-aula00.rst b/content/curso-asyncio-aula00.rst new file mode 100644 index 000000000..5c10a5be4 --- /dev/null +++ b/content/curso-asyncio-aula00.rst @@ -0,0 +1,28 @@ +Curso Python asyncio: Aula 00 - Introdução ao módulo asyncio +############################################################ + +:date: 2016-06-03 17:30 +:tags: python, asyncio +:category: Tutoriais +:slug: curso-asyncio-aula00 +:author: Carlos Maniero +:email: carlosmaniero@gmail.com +:github: carlosmaniero +:twitter: carlosmaniero +:linkedin: carlosmaniero +:facebook: carlosmaniero + + +Primeira aula do curso de asyncio. +Nessa vídeo aula são abordadas as principais diferenças entre Concorrência e Paralelismo. + +Slides: +http://carlosmaniero.github.io/curso-asyncio/aula00/ + +GitHub: +http://github.com/carlosmaniero/ +http://github.com/carlosmaniero/curso-asyncio + +http://carlosmaniero.github.io/ + +.. youtube:: xGoEpCaachs diff --git a/content/curso-asyncio-aula1.rst b/content/curso-asyncio-aula1.rst new file mode 100644 index 000000000..962a2ba4d --- /dev/null +++ b/content/curso-asyncio-aula1.rst @@ -0,0 +1,30 @@ +Curso Python asyncio: Aula 01 - Iterators e Generators +###################################################### + +:date: 2016-06-11 12:30 +:tags: python, asyncio +:category: Tutoriais +:slug: curso-asyncio-aula1 +:author: Carlos Maniero +:email: carlosmaniero@gmail.com +:github: carlosmaniero +:twitter: carlosmaniero +:linkedin: carlosmaniero +:facebook: carlosmaniero + + +Entendendo o conceito de Iterator e Generator. + +Primeira Aula: +https://www.youtube.com/watch?v=xGoEpCaachs + +Slides: +http://carlosmaniero.github.io/curso-asyncio/aula01/ + +GitHub: +http://github.com/carlosmaniero/ +http://github.com/carlosmaniero/curso-asyncio + +http://carlosmaniero.github.io/ + +.. youtube:: jksHpfsarfc diff --git a/content/debugging-em-python-sem-ide.md b/content/debugging-em-python-sem-ide.md new file mode 100644 index 000000000..0495cc291 --- /dev/null +++ b/content/debugging-em-python-sem-ide.md @@ -0,0 +1,242 @@ +title: Debugging em python (sem IDE) +Slug: debugging-em-python-sem-ide +Date: 2015-02-15 22:30 +Tags: python,pdb,ipython,ipdb,debugging +Author: Diego Garcia +Email: drgarcia1986@gmail.com +Github: drgarcia1986 +Site: http://www.codeforcloud.info +Twitter: drgarcia1986 +Linkedin: drgarcia1986 +Category: debugging + + +
+ +
+
+Um dos principais motivos que ainda levam desenvolvedores Python a recorrerem a IDEs pesadas e que requerem instalação é o **debugging**. +Devs que vieram de linguagens como _DotNet_, _Java_ e _Delphi_ por exemplo, estão acostumados a IDEs super pesadas e inchadas que no final das contas, além do debugging, só servem para drenar memória RAM. +Brincadeiras a parte, não a motivos para você não dar uma chance ao **VIM** ou ao **Sublime**, pois para fazer debugging em scripts python, tudo que você precisa é o **PDB**. + + + +# PDB +O `pdb` é um módulo _buit-in_ que funciona como um console interativo, onde é posssível realizar debug de códigos python. +Nele é possível fazer um _step-by-step_ do código, verificando o valor de variaveis, definindo breakpoints, manipulando valores, etc. +É possível inclusive realizer _step-into_ em métodos. Ou seja, tudo que uma boa ferramenta de debug precisa ter. + +## Comandos +Antes de partirmos para prática, é importante conhecer alguns comandos básicos para já começar o uso do pdb de forma efetiva. + +Durante o debugging, eventualmente seu script irá _estacionar_ em pontos de paradas, possívelmente definidos por você, neste momento, os comandos a seguir poderão ser utilizados. + +### q (quit) +Sai da execução do script. + +### n (next) +Avança para a próxima linha do script. + +### p (print) +Executa o comando `print` do python, por exemplo: +```python +> /script.py(1)() +-> foo = "foo var" +(Pdb) p foo +'foo var' +``` +> Vale ressaltar que no exemplo acima, não é necessário utilizar o comando `p`, basta digitar o nome da variável e pressionar `enter`, o efeito seria o mesmo. + +### c (continue) +Avança o debug até o próximo **breakpoint** ou até ocorrer uma **exception**. + +### l (list) +Lista algumas linhas do código que estão em volta da linha atual. +Por padrão serão apresentadas 11 linhas (5 acima e 5 abaixo). + +### s (step into) +Ao realizar a navegação através do comando `n` o debug **não** irá _entrar_ em métodos que possívelmente forem invocados. +Para que o debug entre no método que está sendo invocado na linha corrente, basta trocar o comando `n`, pelo comando `s`. +```python +> /home/user/foo.py(20)() +-> foo.bar('barz') +(Pdb) s +--Call-- +> /home/user/foo.py(3)bar() +-> def bar(self, the_bar): +(Pdb) +``` + +### r (return) +Já o comando `r` libera a execução do script até sair da função atual. + +### b (breakpoint) +Cria um breakpoint em uma determinada linha ou método, por exemplo. +```python +> /script.py(1)() +(Pdb) b 21 +Breakpoint 1 at /script.py:21 +``` +No comando acima, setamos um breakpoint na linha 21 de nosso script. +```python +> /script.py(1)() +(Pdb) b foo +Breakpoint 1 at /script.py:30 +``` +Já no exemplo acima, setamos o breakpoint para o método `foo`. +O pdb informa qual linha ele setou o breakpoint, em nosso exemplo o método `foo` está na linha 30 do script. + +### a (arguments) +O comando `a` mostra os argumentos que foram passados para a função atual. + +```python +> /home/user/foo.py(20)() +-> foo.bar('barz') +(Pdb) s +--Call-- +> /home/user/foo.py(3)bar() +-> def bar(self, the_bar): +(Pdb) a +the_bar = "barz" +``` + +### ENTER +Se você pressionar o `ENTER` sem nenhum comando no pdb, ele irá repetir o último comando executado. + +## Debug na prática +Vamos utilizar um script python simples e didático como exemplo. + +```python +class NumberList(object): + def __init__(self): + self.numbers = list() + + def add(self, number): + if not isinstance(number, (int, float)): + raise TypeError + self.numbers.append(number) + + def sum(self): + result = 0 + for i in self.numbers: + result += i + return result + + +if "__main__" == __name__: + numbers = NumberList() + + numbers.add(5) + assert numbers.sum() == 5 + + numbers.add(10) + assert numbers.sum() == 15 + + print "The End" +``` +Esse script possui uma classe chamada `NumberList` que armazena uma lista de numeros e retorna a soma deles. +Além destas classe, esse script também realiza algumas operações como instanciar essa classe e realizar alguns testes de asserção. +Salve esse script em um arquivo chamado `numbers.py` para ser utilizado em nossos exemplos. + +## Modos de uso do pdb + +Na prática o pdb se assemelha bastante ao prompt interativo do python, com a diferença dos caracteres identificadores. +Enquanto que no prompt interativo do python o identificador é o `>>>`, no pdb o identificador é `(Pdb)`. +Existem algumas maneiras de usar o pdb, depende da forma como você pretende realizer o debug. + +### pdb.py +Uma delas é através da chamada do script `pdb.py` passando como paramêtro o script para ser feito do debug, por exemplo: + +```bash +python -m pdb numbers.py +``` +Isso fará com que o pdb seja iniciado na primeira linha do script `numbers.py`, no caso, a declaração da classe `NumberList()`. +Caso você execute o comando `n`, a próxima linha será o `if "__main__" == __name__:` e assim por diante. +Utilizando desta maneira, você pode verificar linha a linha do script ou _setar_ um breakpoint assim que entrar no debug, por exemplo, se você quer criar um breakpoint na execução do método `sum()` de uma instância da classe `NumberList()`, basta executar o comando `b numbers.sum`. + +```python +(venv)user@machine:~/$ python -m pdb numbers.py +> /home/user/numbers.py(4)() +-> class NumberList(object): +(Pdb) n +> /home/user/numbers.py(20)() +-> if __name__ == "__main__": +(Pdb) n +> /home/user/numbers.py(21)() +-> numbers = NumberList() +(Pdb) n +> /home/user/numbers.py(23)() +-> numbers.add(5) +(Pdb) b numbers.sum +Breakpoint 1 at /home/user/numbers.py:13 +(Pdb) +``` + +Ou para simplificar, também poderiamos setar o breakpoint pelo número da linha. + +```python +(venv)user@machine:~/$ python -m pdb numbers.py +> /home/user/numbers.py(4)() +-> class NumberList(object): +(Pdb) b 13 +Breakpoint 1 at /home/user/numbers.py:13 +(Pdb) +``` +### pdb.set_trace() +Outra forma é utilizando o método `set_trace()` do pacote `pdb`. +Com o `pdb.set_trace()` você pode definir onde será o seu breakpoint via código, por exemplo, faremos uma alteração em nosso script para setar um breakpoint no método `NumberList().sum()`. +```python +class NumberList(object): + def __init__(self): + self.numbers = list() + + def add(self, number): + if not isinstance(number, (int, float)): + raise TypeError + self.numbers.append(number) + + def sum(self): + import pdb + pdb.set_trace() + + result = 0 + for i in self.numbers: + result += i + return result + +""" +Resto do script omitido +""" +``` +Dessa forma, ao executar o script (sem a necessidade de ser via pdb) e passar pelo método `pdb.set_trace()` será iniciado um prompt interativo do pdb. + +```python +(venv)user@machine:~/$ python numbers.py +> /home/user/numbers.py(16)sum() +-> result = 0 +(Pdb) +``` + +## ipdb +Uma das desvantagens do prompt interativo do python é a falta de _syntax highlighting_ e _code completion_, com o pdb não é diferente, porém, assim como podemos recorrer ao [ipython](http://ipython.org/) para isso, também podemos utilizar o [ipdb](https://github.com/gotcha/ipdb). +O `ipdb` é uma espécie de wrapper para o pdb que faz uso das rotinas de debug do `IPython`. +A maneira de uso se assemelha bastante ao pdb, bastando trocar o pacote `pdb` pelo pacote `ipdb`. + +```python +import ipdb + +foo = "foo" +ipdb.set_trace() +bar = "bar" +``` + +Para instalar o ipdb basta utilizar o `pip` + +``` +pip install ipdb +``` +Com certeza recomendo o uso do `ipdb` principalmente por ser mais intuitivo. + +**Referências**
+[Documentação Oficial](https://docs.python.org/2/library/pdb.html)
+[ipdb](https://github.com/gotcha/ipdb) diff --git a/content/debugging-logging.md b/content/debugging-logging.md new file mode 100644 index 000000000..48c16c2b4 --- /dev/null +++ b/content/debugging-logging.md @@ -0,0 +1,66 @@ +Title: Debugging - logging +Date: 2016-11-27 17:48 +Tags: python, debugging, logging +Category: Python +Slug: debugging-logging +Author: Bruno Santana +Email: santanasta@gmail.com +Github: BrunoLSA + + +Achei algo interessante no livro que estou lendo (Automatize tarefas maçantes com Python) e resolvi compartilhar. + +Trata-se do Logging, que ajuda no debug do programa. + +Vejam o exemplo nesse programa, com falha: + + :::python + import logging + logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s') + + + logging.debug('Start of program') + + def factorial(n): + logging.debug('Start of factorial(%s%%)' % (n)) + total = 1 + for i in range(n+1): + total *= i + logging.debug('i is ' + str(i) + ', total is ' + str(total)) + logging.debug('End of factorial(%s%%)' % (n)) + return total + + print(factorial(5)) + logging.debug('End of program') + + +O programa retorna: + + :::python + 2016-11-15 16:17:30,339 - DEBUG - Start of program + 2016-11-15 16:17:30,340 - DEBUG - Start of factorial(5%) + 2016-11-15 16:17:30,340 - DEBUG - i is 0, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - i is 1, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - i is 2, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - i is 3, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - i is 4, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - i is 5, total is 0 + 2016-11-15 16:17:30,340 - DEBUG - End of factorial(5%) + 2016-11-15 16:17:30,340 - DEBUG - End of program + 0 + +Dessa forma, podemos ver o passo a passo que o programa está realizando e identificar onde está o erro. No caso, vemos que para corrigir o problema, devemos alterar o **for i in range(n+1):** para **for i in range(1, n+1):**. +Quando o desenvolvedor não quiser mais visualizar as mensagens de logging, basta chamar **logging.disable(logging.CRITICAL)** logo embaixo do **import logging**. Essa função faz com que não seja necessário alterar o programa removendo todas as chamadas de logging manualmente. + +Também é possível gravar as mensagens de log num arquivo, ao invés de mostrá-las na tela. A função aceita o argumento **filename**. + + :::python + import logging + logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s') + + +Lado negativo do uso dessa função: a leitura do código fica difícil, por causa desse monte de logging.debug no meio do código. Para evitar isso, pode-se usar um decorator. + + + + diff --git a/content/deploy-com-dokku.md b/content/deploy-com-dokku.md new file mode 100644 index 000000000..2fcfb097a --- /dev/null +++ b/content/deploy-com-dokku.md @@ -0,0 +1,144 @@ +Title: Deploy rápido e simples com Dokku +Slug: deploy-rapido-simples-com-dokku +Date: 2016-11-23 17:30 +Tags: python,deploy,django,dokku +Author: Júnior Carvalho +Email: joseadolfojr@gmail.com +Github: juniorcarvalho +twitter: @joseadolfojr +Linkedin: juniorcarvalhorj +Category: Python, Deploy + +Sempre busquei alternativas para deploy simples como o heroku. Vou mostrar neste passo-a-passo uma forma simples e rápida utilizando o [Dokku]. + +[Dokku] é a menor implementação PaaS que você já viu. De uma forma simples e rápida consegue-se configurar um servidor para deploy. Se existe alguma dúvida sobre PaaS, SaaS, etc., uma pesquisa rápida no google vai retornar várias referências. + +Nesse exemplo vou utilizar uma vps básica na [DigitalOcean]. Uma máquina 512 MB / 20 Gb/ 1 CPU, rodando Ubuntu Server 16.04.1. É possível criar uma máquina já com [Dokku] instalado e pré-configurado mas vou fazer com uma máquina 'limpa' para servir de base para outras vps. +### Instalando +Com o servidor em execução vamos acessar via ssh pelo terminal. No caso de utilizar OS Windows, utilize o [putty] para acessar o servidor. + +``` +ssh [ip-do-servidor] -l root +``` +No primeiro acesso confirme com yes o questionamento sobre a autenticidade. No caso da [DigitalOcean] vai ser solicitado a mudança de senha nesse primeiro acesso. + +Seguindo a documentação do [Dokku] vamos realizar a instação. Este processo demora +- uns 10 minutos. +``` +wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh +sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh +``` +Finalizado o script de instação vamos adicionar a chave publica de nosso usuário de desenvolvimento para conseguir fazer o deploy no servidor recem criado. +### Chaves +Na nossa máquina de desenvolvimento, vamos checar se nosso usuário tem uma chave pública: +``` +ls -al ~/.ssh +``` +Por padrão os arquivos das chaves públicas podem ser: + + - id_dsa.pub + - id_ecdsa.pub + - id_ed25519.pub + - id_rsa.pub + + +No meu caso eu tenho o id_rsa.pub, então vou ler o conteúdo, seleciona-lo e copiar: +``` +cat ~/.ssh/id_rsa.pub +``` + +Para gerar a chave: (caso não exista nenhum arquivo .pub na pasta ~/.ssh) +``` +ssh-keygen -t rsa +``` +Aceite as três opções pedidas por default. (não inserir password) +Para OS Windows achei este artigo. [ssh Windows] (não testei) +### Inserindo a chave pública no servidor +Com nossa chave pública copiada (ctrl+c) vamos abrir o browser e digitar o ip do nosso servidor. Vai aparecer uma tela como a da imagem a seguir: +
+ +
+
+No campo Public Key, colamos nossa chave (Ctrl+V) e depois é so clicar em Finish Setup. Feito isto você vai ser redirecionado para página da documentação do [Dokku]. +### Criando nossa APP +No terminal, conectado no nosso servidor: +``` +dokku apps:create [nome-app] +``` +No meu caso: +``` +dokku apps:create fjfundo +``` +Para listar as apps existentes: +``` +dokku apps +``` +Quando você cria um novo aplicativo, por padrão o [dokku] nao fornece nenhum banco de dados como MySql ou PostgreSQL. É preciso instalar plugins. [Dokku] tem plugins oficiais para banco de dados. Neste passo-a-passo vou utilizar o PostgreSQL. + +### Instalando o plugin postgres e configurando o serviço de banco de dados +No terminal, conectado no nosso servidor: +``` +sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git +``` +Criando o serviço postgres: + +``` +dokku postgres:create [nome-servico] +``` +No meu caso: +``` +dokku postgres:create fjfundo-database +``` +Vamos criar o link entre os serviços de banco de dados e nossa app. +``` +dokku postgres:link [nome-servico] [nome-app] +``` +No meu caso: +``` +dokku postgres:link fjfundo-database fjfundo +``` + +### Configurando e executando nosso primeiro deploy. +Na nossa máquina cliente vamos configurar o git para fazer o primeiro deploy. +Vamos adicionar nosso repositorio remoto dokku da seguinte forma: +``` +git remote add dokku dokku@[ip-do-servidor]:[nome-app] +``` +No meu caso: +``` +git remote add dokku dokku@xxx.xxx.xxx.xxx:fjfundo +``` +No meu caso antes de fazer o deploy eu tenho que configurar algumas variáveis de ambiente como DEBUG e SECRET_KEY. Para esta configuração executo os comandos no servidor. Vou seguir a documentação existente em [Environment Variables] +``` +dokku config:set fjfundo DEBUG='False' +dokku config:set fjfundo SECRET_KEY='sua secret_key' +``` +Para listar as variáveis de ambiente de nossa app: +``` +dokku config [nome da app] +``` +Feito isto vamos ao nosso primeiro deploy: ( na nossa máquina cliente/dev) +``` +git push dokku master --force +``` +Pronto! Agora é so acessar nossa aplicação. No final do deploy o [dokku] mostra a url de conexão. Caso precise obter a url use o comando no servidor: +``` +dokku url [nome-app] +``` +### Criando as tabelas do banco de dados + +Nossa app está no ar mais ainda não tem as tabelas de nossa base de dados. +``` +dokku run fjfundo python manage.py migrate +``` +Troque fjfundo pelo nome da sua app. + +### Considerações finais +Ainda estou estudando e aprendendo a utilizar o [dokku]. Utilizo apenas em ambiente de desenvolvimento mas pretendo utilizar em produção. +Ainda tenho muito que aprender e fica aqui meu email, joseadolfojr@gmail.com, para quem tiver alguma dúvida e quizer também contribuir para meus estudos. + + +[Dokku]: http://dokku.viewdocs.io/dokku/ +[DigitalOcean]: https://m.do.co/c/2f45101e7ccf +[putty]:http://www.putty.org/ +[ssh Windows]: http://adrianorosa.com/blog/seguranca/como-criar-ssh-key-pair-windows.html +[Environment Variables]: http://dokku.viewdocs.io/dokku/configuration/environment-variables/ \ No newline at end of file diff --git a/content/desenvolvendo-com-bottle-parte-1.md b/content/desenvolvendo-com-bottle-parte-1.md new file mode 100644 index 000000000..10948dfba --- /dev/null +++ b/content/desenvolvendo-com-bottle-parte-1.md @@ -0,0 +1,177 @@ +Title: Desenvolvendo com Bottle - Parte 1 +Slug: desenvolvendo-com-bottle-parte-1 +Date: 2014-12-03 15:05 +Tags: bottle,python +Author: Eric Hideki +Email: eric8197@gmail.com +Github: erichideki +Site: http://ericstk.wordpress.com +Twitter: erichideki +Category: begginers, bottle, tutorial + +# Texto originalmente escrito em: + +[https://realpython.com/blog/python/developing-with-bottle-part-1/] + +Eu amo [bottle]. Ele é simples, rápido e poderoso micro-framework Python, perfeito para pequenas aplicações web e rápida prototipação. É também perfeita ferramenta de ensino para aqueles que estão começando agora com desenvolvimento web. + +Vamos dar um rápido exemplo. + +> Este tutorial espera que você esteja em um ambiente baseado em Unix - e.g, Mac OSX, sistemas Linux, ou no Linux rodando uma Virtual Machine no Windows. Eu irei estar usando Sublime Text 2 como meu editor de texto. + +#Começando + +Primeiramente, vamos criar um diretório para trabalhar: + +``` +$ mkdir bottle +$ cd bottle +``` + +Depois, você precisa ter pip, virtualenv, e git instalados. + +[virtualenv] é uma ferramenta Python que torna fácil gerenciar módulos Python necessários para um particular projeto. Ele também mantém os módulos isolados para que não entre em conflito com outros projetos.[pip], entretanto, é um gerenciador de pacotes usado para gerenciar a instalaçao de bibliotecas e módulos. + +Para ajudar com a instalação do pip(e todas as dependências) em um ambiente Unix, siga as instruções nesse [Gist]. Se você está no Windows, por favor veja esse [vídeo] para sua ajuda. + +Uma vez com o pip instalado, execute os seguintes comandos para instalar o virtualenv: + +``` +$ pip install virtualenv +``` + +Agora nós podemos facilmente configurar nosso ambiente local executando: + +``` +$ virtualenv --no-site-packages testenv +$ source testenv/bin/activate +``` + +Instalar o Bottle: + +``` +$ pip install bottle +``` + +Agora criamos o arquivo *requirements.txt*, que permitirá você instalar os exatos módulos e dependências iguais no caso de você querer usar esse aplicativo em qualquer outro local. Clique [aqui] para aprender mais. + +``` +pip freeze > requirements.txt +``` + +Finalmente, vamos colocar nossa aplicação em um controle de versão usando Git. Para mais informações sobre o Git, por favor veja esse [site], que inclui instruções de instalação. + +``` +$ git init +$ git add . +$ git commit -m "initial commit" +``` + +##Escrevendo sua aplicação + +Agora estamos preparados para escrever nossa aplicação com Bottle. Crie seu arquivo de aplicação, *app.py*, que irá carregar todo o nosso primeiro aplicativo: + +``` +import os +from bottle import route, run, template + +index_html = '''Minha primeira aplicação! Por {{ autor }}''' + +@route('/:qualquer') +def alguma_coisa(qualquer=''): + return template(index_html, autor=qualquer) + +@route('/') +def index(): + return template(index_html, autor='Seu nome aqui:') + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 8080)) + run(host='0.0.0.0', port=port, debug=True) +``` + +Salve o arquivo. + +Agora você pode executar sua aplicação localmente: + +``` +$ python app.py +``` + +Você poderá ser capaz de conectar a [http://localhost:8080/abc] e ver sua aplicação rodando! +Mude ```abc``` para seu nome. Dê refresh no navegador. + +#O que tá acontecendo? + +1. O decorator ```@route``` diz que a aplicação deve interpretar o caminho depois do ```/``` como variável ```qualquer```. +2. Isto é passado para a função sendo como um argumento```(def alguma_coisa(qualquer='')```. +3. Nós então passamos isto para a função do template sendo um argumento(```autor=qualquer```) +4. O template então renderiza a variável autor com ```{{ autor }}``` + +#Shell script + +Quer começar de forma rápida? Crie um inicializador de aplicação em poucos segundos usando o Shell script. + +``` +mkdir bottle +cd bottle +pip install virtualenv +virtualenv --no-site-packages testenv +source testenv/bin/activate +pip install bottle +pip freeze > requirements.txt +git init +git add . +git commit -m "initial commit" + +cat >app.py <=}}{{ autor }}<%={{ }}=%>''' + +@route('/:qualquer') +def alguma_coisa(qualquer=''): + return template(index_html, autor=qualquer) + +@route('/') +def index(): + return template(index_html, autor='Seu nome aqui:') + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 8080)) + run(host='0.0.0.0', port=port, debug=True) +EOF + +chmod a+x app.py + +git init +git add . +git commit -m "Updated" +``` + +Baixe esse script através dessa [Gist-list], e então execute o seguinte comando: + +``` +$ bash bottle.sh +``` + +#Próximos passos + +A partir desse ponto, é tão fácil adicionando uma nova ```@route```-decorated functions() para criar novas páginas, assim como fizemos com essas duas páginas. + +Criar o HTML é simples: Nessa aplicação, nós apenas adicionamos HTML na mesma linha e arquivo.Isto é fácil de modificar(usando, por exemplo, ```open('index.html').read())``` para ler o template de um arquivo. + +Referências para a [documentação] do Bottle para mais informações. + +[https://realpython.com/blog/python/developing-with-bottle-part-1/]:https://realpython.com/blog/python/developing-with-bottle-part-1/ +[bottle]:http://bottlepy.org/docs/stable/ +[virtualenv]:https://pypi.python.org/pypi/virtualenv +[pip]:https://pypi.python.org/pypi/pip +[Gist]:https://gist.github.com/mjhea0/5692708 +[vídeo]:https://www.youtube.com/watch?v=MIHYflJwyLk +[aqui]:https://pip.pypa.io/en/latest/user_guide.html#requirements-files +[site]:http://git-scm.com/book/pt-br/v1/Primeiros-passos-No%C3%A7%C3%B5es-B%C3%A1sicas-de-Git +[http://localhost:8080/abc]:http://localhost:8080/abc +[Gist-list]:https://gist.github.com/mjhea0/5784132 +[documentação]:http://bottlepy.org/docs/dev/ diff --git a/content/desenvolvendo-para-google-app-engine-com-tekton.md b/content/desenvolvendo-para-google-app-engine-com-tekton.md new file mode 100644 index 000000000..154cf7085 --- /dev/null +++ b/content/desenvolvendo-para-google-app-engine-com-tekton.md @@ -0,0 +1,65 @@ +Title: Criação de aplicações no Google App Engine com o Tekton +Slug: desenvolvendo-para-google-app-engine-com-tekton +Date: 24-05-2015 +Tags: tekton, python, google app engine, tutorial +Author: Guido Luz Percú +Email: guidopercu@gmail.com +Github: GuidoBR +Site: http://www.guidopercu.com.br/ +Twitter: oumguido +Category: google app engine + +## Google App Engine (GAE) +É a plataforma de Cloud Computing do Google, com ela você pode desenvolver e hospedar aplicações usando Python (2.7) que escalam facilmente, [pagando muito pouco por isso]. + +As desvantagens (em relação a outras plataformas de nuvem, como o Heroku por exemplo) são: +- Você terá que desenvolver pensando na plataforma (banco de dados NoSQL, por isso o [Django não é recomendável].). +- Versão do Python é antiga e não há planos para mudar isso no momento. + +## Tekton +É um framework para desenvolvimento Web especialmente pensado para uso no Google App Engine. Nele podemos aproveitar o melhor do Django (scaffold, código HTML e validação de formulários a partir de modelos, apps isoladas) sem perder as vantagens que o GAE nos oferece. + +## Como iniciar +O primeiro passo é baixar o [SDK do Google App Engine], com isso pronto podemos começar a conhecer o Tekton. + +Em seguida, vamos baixar a aplicação template. +``` +$ wget https://github.com/renzon/tekton/archive/master.zip +$ unzip master && rm master.zip +$ mv tekton-master projeto_appengine && cd projeto_appengine +``` +Nesse ponto podemos explorar e conhecer a estrutura de diretórios. +``` +└── backend + ├── appengine + ├── apps + ├── build_scripts + ├── test + └── venv +``` + +``` +$ cd backend/venv/ && ./venv.sh +$ source ./bin/activate +``` +Com o ambiente virtual pronto, tudo deve estar funcionando. Para testar, +vamos utilizar o próprio servidor que vem com o pacote antes de subir parao GAE. + +``` +cd ../appengine && dev_appserver.py . +``` + +Tudo certo! Você deve estar vendo o projeto template no seu localhost:8080 + +Para realizar o deploy no App Engine: + +``` +appcfg.py update . --oauth2 +``` +Você pode conhecer mais sobre o projeto no [Github], no [grupo de discussões] ou nas vídeo aulas gratuitas no [Youtube]. +[SDK do Google App Engine]:https://cloud.google.com/appengine/downloads +[pagando muito pouco por isso]:https://cloud.google.com/appengine/pricing +[Django não é recomendável]:http://imasters.com.br/desenvolvimento/app-engine-e-django-hell/ +[Youtube]:https://www.youtube.com/playlist?list=PLA05yVJtRWYRGIeBxag8uT-3ftcMVT5oF +[Github]:https://github.com/renzon/tekton +[grupo de discussões]:https://groups.google.com/forum/#!forum/tekton-web diff --git a/content/diferenca-operadores.md b/content/diferenca-operadores.md new file mode 100644 index 000000000..bac3444b3 --- /dev/null +++ b/content/diferenca-operadores.md @@ -0,0 +1,109 @@ +Title: Diferença entre == e is +Slug: diferenca-operadores +Date: 2015-11-27 14:00 +Tags: python,dica,operadores +Author: Gildásio Júnior +Email: gjuniioor@protonmail.ch +Github: gjuniioor +Site: https://gjuniioor.github.io +Category: Python +Status: draft + +Diferença entre == e is +----------- + +Olá, galera, tudo tranquilo? + +Estava conversando com um amigo que está estudando python e tudo mais, e então ele veio com a seguinte dúvida: + +> Qual a diferença entre == e is no python? + +Para quem não sabe, no python tem o operador *is* que "tem a mesma função do *==*. Veja: + +```python +>>> x = 10 +>>> y = 10 +>>> x == y +True +>>> x is y +True +``` + +Mas... + +```python +>>> x = 1000 +>>> y = 1000 +>>> x == y +True +>>> x is y +False +``` + +Viu só? Pois bem, o que que acontece então?? + +O python tem um mecanismo interessante nesse ponto... Quando se tratam de *coisas pequenas* ele utiliza de ponteiros para apontar outros rótulos para um mesmo endereço de memória. Quando o que é armazenado na variável já começa a crescer, fica maior e tal, ele já não usa disso, para não pesar, mas sim de outro endereço ... + +Seria um cache que ele faz de alguns tipos de objetos, entre eles estão int e string, por exemplo. Float e dicionário já não são assim. + +Para ter uma ideia melhor disso, vamos ver os endereços que as variáveis ocupam e o resultado da comparação: + +```python +>>> x = 10 +>>> y = 10 +>>> hex(id(x)) +'0x98a1844' +>>> hex(id(y)) +'0x98a1844' +>>> x == y +True +>>> x is y +True +``` + +Perceba que ele pega o mesmo endereço ... Agora, se colocarmos valores maiores: + +```python +>>> x = 1000 +>>> y = 1000 +>>> hex(id(x)) +'0x98e5520' +>>> hex(id(y)) +'0x98e5508' +>>> x == y +True +>>> x is y +False +``` + +Ou seja, o *is* (como a tradução mostra) vai verificar se algo é aquilo a que a comparação está se referindo, ou seja, se são a mesma coisa. Já o *==* vai analisar se são iguais, assim como o esperado. + +Mais algumas demonstrações: + +* Float: + +```python +>>> x = 1.0 +>>> y = 1.0 +>>> x == y +True +>>> x is y +False +``` + +* Dict: + +```python +>>> x = [1] +>>> y = [1] +>>> x == y +True +>>> x is y +False +``` + +> Não há motivo para me aprofundar tanto aqui, é apenas uma dica rápida. Espero que resolva os problemas de dúvidas de quem necessitar ... Tem mais algum caso como esse? Quer tirar alguma dúvida do tipo? Comenta ai ou entra em contato (olha no topo da página). + +Vlw pessoal, até mais ver!! + +> Texto originalmente postado aqui: [https://gjuniioor.github.io/blog/python-diferenca-operadores/](https://gjuniioor.github.io/blog/python-diferenca-operadores/). diff --git a/content/django-ci-github-actions.rst b/content/django-ci-github-actions.rst new file mode 100644 index 000000000..fb4bbdbab --- /dev/null +++ b/content/django-ci-github-actions.rst @@ -0,0 +1,21 @@ +Criando um CI de uma aplicação Django usando Github Actions +########################################################### + +:date: 2020-01-24 12:10 +:tags: python, django, github actions +:category: Python +:slug: django-ci-github-actions +:author: Lucas Magnum +:email: lucasmagnumlopes@gmail.com +:github: lucasmagnum +:linkedin: lucasmagnum + +Fala pessoal, tudo bom? + +Nos vídeo abaixo vou mostrar como podemos configurar um CI de uma aplicação Django usando Github Actions. + +`https://www.youtube.com/watch?v=KpSlY8leYFY `_. + + +.. youtube:: KpSlY8leYFY + diff --git a/content/django-rest-framework-class-based-views.md b/content/django-rest-framework-class-based-views.md new file mode 100644 index 000000000..c5dac7958 --- /dev/null +++ b/content/django-rest-framework-class-based-views.md @@ -0,0 +1,155 @@ +title: Django Rest Framework - #3 Class Based Views +Slug: django-rest-framework-class-based-views +Date: 2018-02-15 23:00 +Tags: Python, Django, REST +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Python, Django, REST + +* 0 - [Quickstart][10] +* 1 - [Serialization][11] +* 2 - [Requests & Responses][12] +* 3 - **Class based views** + +Este post é continuação do post [Django Rest Framework Requests & Responses][12]. + +Finalmente chegamos as views baseadas em classes. A grande vantagem é que com poucas linhas de código já temos nossa API pronta. + +Veja como fica a [views.py](https://github.com/rg3915/drf/blob/b0aa989ffc756e6dc5f65e172dfb43d47127d743/core/views.py): + +```python +from django.http import Http404 +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from core.models import Person +from core.serializers import PersonSerializer + + +class PersonList(APIView): + """ + List all persons, or create a new person. + """ + + def get(self, request, format=None): + persons = Person.objects.all() + serializer = PersonSerializer(persons, many=True) + return Response(serializer.data) + + def post(self, request, format=None): + serializer = PersonSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class PersonDetail(APIView): + """ + Retrieve, update or delete a person instance. + """ + + def get_object(self, pk): + try: + return Person.objects.get(pk=pk) + except Person.DoesNotExist: + raise Http404 + + def get(self, request, pk, format=None): + person = self.get_object(pk) + serializer = PersonSerializer(person) + return Response(serializer.data) + + def put(self, request, pk, format=None): + person = self.get_object(pk) + serializer = PersonSerializer(person, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk, format=None): + person = self.get_object(pk) + person.delete() + return Response(status=status.HTTP_204_NO_CONTENT) +``` + +E `urls.py`: + +```python +urlpatterns = [ + path('persons/', views.PersonList.as_view()), + path('persons//', views.PersonDetail.as_view()), +] +``` + +## Usando Mixins + +Repare que no exemplo anterior tivemos que definir os métodos `get()`, `post()`, `put()` e `delete()`. Podemos reduzir ainda mais esse código com o uso de mixins. + +```python +from rest_framework import mixins +from rest_framework import generics +from core.models import Person +from core.serializers import PersonSerializer + + +class PersonList(mixins.ListModelMixin, + mixins.CreateModelMixin, + generics.GenericAPIView): + queryset = Person.objects.all() + serializer_class = PersonSerializer + + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + +class PersonDetail(mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + generics.GenericAPIView): + queryset = Person.objects.all() + serializer_class = PersonSerializer + + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) +``` + +## Usando generic class-based views + +E para finalizar usamos `ListCreateAPIView` e `RetrieveUpdateDestroyAPIView` que já tem todos os métodos embutidos. + +```python +from rest_framework import generics +from core.models import Person +from core.serializers import PersonSerializer + + +class PersonList(generics.ListCreateAPIView): + queryset = Person.objects.all() + serializer_class = PersonSerializer + + +class PersonDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Person.objects.all() + serializer_class = PersonSerializer +``` + +Versão final de [views.py](https://github.com/rg3915/drf/blob/263ca63e5c8e2dd2e0f5d6c88c5733fcfdab4f74/core/views.py). + +Abraços. + +[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html +[11]: http://pythonclub.com.br/django-rest-framework-serialization.html +[12]: http://pythonclub.com.br/django-rest-framework-requests-responses.html \ No newline at end of file diff --git a/content/django-rest-framework-quickstart.md b/content/django-rest-framework-quickstart.md new file mode 100644 index 000000000..f1218a4e0 --- /dev/null +++ b/content/django-rest-framework-quickstart.md @@ -0,0 +1,213 @@ +title: Django Rest Framework Quickstart +Slug: django-rest-framework-quickstart +Date: 2015-11-17 14:00 +Tags: Python, Django, REST +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Python, Django, REST + +Veremos aqui uma forma rápida de criar uma API REST com [Django Rest Framework][0]. + +> Este artigo foi atualizado em 14 de Fevereiro de 2018. + +Este artigo está usando: + +* Python 3.5.2 +* Django 2.0.2 +* djangorestframework 3.7.7 + +Favor clonar o projeto do [GitHub](https://github.com/rg3915/drf#clonando-o-projeto), favor ler o README para instalação. + +Repare nas alterações das urls na nova versão do Django. + +```python +urls.py +from django.urls import include, path +from django.contrib import admin + +urlpatterns = [ + path('', include('core.urls')), + path('admin/', admin.site.urls), +] +``` + +```python +# core/urls.py +from django.urls import path +from core import views + +urlpatterns = [ + path('persons/', views.person_list), + path('persons//', views.person_detail), +] +``` + +Além disso, tivemos alterações significativas em [settings.py](https://github.com/rg3915/drf/blob/master/myproject/settings.py). + +**Obs**: *Tem coisas que é melhor nem traduzir. ;)* + +* 0 - **Quickstart** +* 1 - [Serialization][11] +* 2 - [Requests & Responses][12] +* 3 - [Class based views][13] + +> **Obs**: se você não sabe [Django][3] sugiro que leia este [tutorial][4] antes. + +## Começando + +```bash +$ python3 -m venv .venv +$ source env/bin/activate +$ mkdir drf-quickstart +$ cd drf-quickstart +$ pip install django djangorestframework +$ pip freeze > requirements.txt +$ django-admin.py startproject myproject . # tem um ponto '.' aqui +$ python manage.py startapp core +$ python manage.py migrate +$ python manage.py createsuperuser --username='admin' --email='' +``` + +Veja o meu requirements.txt + +```bash +dj-database-url==0.4.2 +Django==2.0.2 +django-extensions==1.9.9 +django-filter==1.1.0 +djangorestframework==3.7.7 +drf-nested-routers==0.90.0 +python-decouple==3.1 +``` + +## Editando `settings.py` + +Abra o arquivo `settings.py` e em `INSTALLED_APPS` acrescente + +```python +INSTALLED_APPS = ( + ... + 'rest_framework', +) + +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), + 'PAGE_SIZE': 10 +} +``` + +## Editando `serializers.py` + +Crie o arquivo + +```bash +$ cd core/ +$ touch serializers.py +``` + +Edite + +```python +from django.contrib.auth.models import User, Group +from rest_framework import serializers + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = User + fields = ('url', 'username', 'email', 'groups') + + +class GroupSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = Group + fields = ('url', 'name') +``` + +## Editando `views.py` + +```python +from django.contrib.auth.models import User, Group +from rest_framework import viewsets +from core.serializers import UserSerializer, GroupSerializer + + +class UserViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + """ + queryset = User.objects.all().order_by('-date_joined') + serializer_class = UserSerializer + + +class GroupViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows groups to be viewed or edited. + """ + queryset = Group.objects.all() + serializer_class = GroupSerializer +``` + + +## Editando `urls.py` + + +```python +from django.conf.urls import url, include +from rest_framework import routers +from core import views + +router = routers.DefaultRouter() +router.register(r'users', views.UserViewSet) +router.register(r'groups', views.GroupViewSet) + +urlpatterns = [ + url(http://github.com/r'%5E',%20include(router.urls)), + url(http://github.com/r'%5Eapi-auth/',%20include('rest_framework.urls',%20namespace='rest_framework')) +] +``` + +## Rodando a API + +Abra duas abas no terminal, numa rode a aplicação. + +```bash +$ python manage.py runserver +``` + +![drf02](images/regisdasilva/drf02.png) + +Na outra teste a API. + +```bash +curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/users/ +``` + +onde `admin:admin` equivale a `username:password`. + +Experimente com [httpie][7] + +```bash +http -a admin:admin http://127.0.0.1:8000/users/ +``` + +> **Atenção**: se você receber erro 301, muito provavelmente é porque você esqueceu da barra `/` no final da url. + +![drf01](images/regisdasilva/drf01.png) + +Veja o código no [GitHub][8]. + +> Haverá continuação ... + +[0]: http://www.django-rest-framework.org/ +[3]: https://www.djangoproject.com/ +[4]: http://pythonclub.com.br/tutorial-django-17.html +[7]: https://github.com/jakubroztocil/httpie#installation +[8]: https://github.com/rg3915/drf-quickstart.git +[11]: http://pythonclub.com.br/django-rest-framework-serialization.html +[12]: http://pythonclub.com.br/django-rest-framework-requests-responses.html +[13]: http://pythonclub.com.br/django-rest-framework-class-based-views.html \ No newline at end of file diff --git a/content/django-rest-framework-requests-responses.md b/content/django-rest-framework-requests-responses.md new file mode 100644 index 000000000..0f8172c4c --- /dev/null +++ b/content/django-rest-framework-requests-responses.md @@ -0,0 +1,121 @@ +title: Django Rest Framework - #2 Requests and Responses +Slug: django-rest-framework-requests-responses +Date: 2018-02-14 23:00 +Tags: Python, Django, REST +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Python, Django, REST + +* 0 - [Quickstart][10] +* 1 - [Serialization][11] +* 2 - **Requests & Responses** +* 3 - [Class based views][12] + +Este post é continuação do post [Django Rest Framework Serialization][11]. + +O uso de *requests* e *responses* torna nossa api mais flexível. A funcionalidade principal do objeto **Request** é o atributo `request.data`, que é semelhante ao `request.POST`, mas é mais útil para trabalhar com APIs. + +## Objeto Response + +Introduzimos aqui um objeto `Response`, que é um tipo de `TemplateResponse` que leva conteúdo não renderizado e usa a negociação de conteúdo para determinar o tipo de conteúdo correto para retornar ao cliente. + +```python +return Response(data) # Renderiza para o tipo de conteúdo conforme solicitado pelo cliente. +``` + +Repare também no uso de *status code* pré definidos, exemplo: `status.HTTP_400_BAD_REQUEST`. + +E usamos o decorador `@api_view` para trabalhar com funções. Ou `APIView` para classes. + +Nosso código ficou assim: + +```python +# views.py +from rest_framework import status +from rest_framework.decorators import api_view +from rest_framework.response import Response +from core.models import Person +from core.serializers import PersonSerializer + + +@api_view(['GET', 'POST']) +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 Response(serializer.data) + + elif request.method == 'POST': + serializer = PersonSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET', 'PUT', 'DELETE']) +def person_detail(request, pk): + """ + Retrieve, update or delete a person instance. + """ + try: + person = Person.objects.get(pk=pk) + except Person.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + if request.method == 'GET': + serializer = PersonSerializer(person) + return Response(serializer.data) + + elif request.method == 'PUT': + serializer = PersonSerializer(person, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + elif request.method == 'DELETE': + person.delete() + return Response(status=status.HTTP_204_NO_CONTENT) +``` + +Veja no [GitHub](https://github.com/rg3915/drf/commit/69205da9262415eaf83ff04f22a635e912880a60). + + +## Usando sufixo opcional + +Em `core/urls.py` acrescente + +```python +from rest_framework.urlpatterns import format_suffix_patterns + +... + +urlpatterns = format_suffix_patterns(urlpatterns) +``` + +E em `views.py` acrescente `format=None` como parâmetro das funções a seguir: + +```python +def person_list(request, format=None): + +def person_detail(request, pk, format=None): +``` + +Com isso você pode chamar a api da seguinte forma: + +```bash +http http://127.0.0.1:8000/persons.json # ou +http http://127.0.0.1:8000/persons.api +``` + +Até a próxima. + +[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html +[11]: http://pythonclub.com.br/django-rest-framework-serialization.html +[12]: http://pythonclub.com.br/django-rest-framework-class-based-views.html \ No newline at end of file diff --git a/content/django-rest-framework-serialization.md b/content/django-rest-framework-serialization.md new file mode 100644 index 000000000..9b313d0cc --- /dev/null +++ b/content/django-rest-framework-serialization.md @@ -0,0 +1,450 @@ +title: Django Rest Framework Serialization +Slug: django-rest-framework-serialization +Date: 2016-04-24 18:00 +Tags: Python, Django, REST +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Python, Django, REST + +Eu resolvi estudar um pouco mais de [DRF][0] depois do tutorial do [Hugo Brilhante][1] na [Python Brasil 11][2]. + +> Este artigo foi atualizado em 14 de Fevereiro de 2018. + +Este artigo está usando: + +* Python 3.5.2 +* Django 2.0.2 +* djangorestframework 3.7.7 + +Favor clonar o projeto do [GitHub](https://github.com/rg3915/drf#clonando-o-projeto), favor ler o README para instalação. + +> **Obs**: se você não sabe Django sugiro que leia este [tutorial][4] antes. + +**Obs**: *Tem coisas que é melhor nem traduzir. ;)* + +* 0 - [Quickstart][10] +* 1 - **Serialization** +* 2 - [Requests & Responses][11] +* 3 - [Class based views][12] + +Pra quem não sabe, para usar API Web usamos REST, no caso, [Django Rest Framework][0], framework web do [Django][3]. + +> **Nota:** este tutorial não é exatamente igual ao do Hugo, é baseado nele. E baseado também em [Tutorial 1: Serialization][9]. + +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](images/regisdasilva/person.jpg) + + +## Configurando um novo ambiente + +```bash +$ python3 -m venv .venv +$ source .venv/bin/activate +$ mkdir drf; cd drf +$ pip install django==2.0.2 djangorestframework==3.7.7 +$ 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 + +```bash +dj-database-url==0.4.2 +Django==2.0.2 +django-extensions==1.9.9 +django-filter==1.1.0 +djangorestframework==3.7.7 +drf-nested-routers==0.90.0 +python-decouple==3.1 +``` + +## Step-0 Projeto inicial + +Abra o arquivo `settings.py` e em `INSTALLED_APPS` acrescente + +```python +INSTALLED_APPS = ( + ... + 'rest_framework', + 'core', +) +``` + +## Step-1 Serializer + +### `models.py`: Criando o modelo `Person` + +```python +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][5] e desserialização das instâncias de `person` em uma representação JSON. + +```bash +$ cd core/ +$ touch serializers.py +``` + +Edite + +```python +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 + +```bash +$ ./manage.py makemigrations core +$ ./manage.py migrate +``` + +### Trabalhando com a serialização + +Abra o `shell` do Django. + +```bash +$ ./manage.py shell +``` + +Primeiro vamos criar uma pessoa. + +```python +>>> 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. + +```python +>>> 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`. + +```python +>>> 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. + +```python +>>> 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: + +```python +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. + +```bash +$ ./manage.py shell +``` + +```python +>>> 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` + +```python +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. + + +```python +@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. + +```python +@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`. + +```python +from django.urls import path +from core import views + +urlpatterns = [ + path('persons/', views.PersonList.as_view()), + path('persons//', views.PersonDetail.as_view()), +] +``` + +E acrescente a seguinte linha em `myproject/urls.py`. + +```python +from django.urls import include, path +from django.contrib import admin + +urlpatterns = [ + path('', include('core.urls')), + path('admin/', admin.site.urls), +] +``` + +## Instalando `httpie` + +Podemos usar o [curl][6], mas o [httpie][7] é mais amigável, e escrito em Python. + +```bash +$ sudo pip install httpie +``` + +Vamos usar o `httpie` digitando + +```bash +$ 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 + +```bash +$ 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 + +```bash +$ 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][8], mas terá que ver os *commits* para ver os passos. + +[0]: http://www.django-rest-framework.org/ +[1]: https://github.com/hugobrilhante/drf-tutorial-pybr11 +[2]: http://pythonbrasil.github.io/pythonbrasil11-site/ +[3]: https://www.djangoproject.com/ +[4]: http://pythonclub.com.br/tutorial-django-17.html +[5]: https://pt.wikipedia.org/wiki/Serializa%C3%A7%C3%A3o +[6]: http://curl.haxx.se/ +[7]: https://github.com/jakubroztocil/httpie#installation +[8]: https://github.com/rg3915/drf.git +[9]: http://www.django-rest-framework.org/tutorial/1-serialization/ +[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html +[11]: http://pythonclub.com.br/django-rest-framework-requests-responses.html +[12]: http://pythonclub.com.br/django-rest-framework-class-based-views.html \ No newline at end of file diff --git a/content/django_na_pratica_parte_1.rst b/content/django_na_pratica_parte_1.rst new file mode 100644 index 000000000..d09caf94d --- /dev/null +++ b/content/django_na_pratica_parte_1.rst @@ -0,0 +1,276 @@ +Django na prática - Hello World +############################################# + +:date: 2015-10-26 09:25 +:tags: python, django, django-na-pratica +:category: Python +:slug: django-na-pratica-aula-01 +:author: Lucas Magnum +:email: lucasmagnumlopes@gmail.com +:github: lucasmagnum +:linkedin: lucasmagnum + + +Esse é o primeiro post do Curso **django na prática** onde você aprenderá tudo que precisa para criar um sistema web :D + +Utilizaremos a versão 1.8 do Django com Python 3! + + +========== +Requisitos +========== + +* Linux (Ubuntu) +* Python 3.X +* Virtualenv +* pip + + +Se precisar de ajuda para instalar o pip, você pode utilizar esse `tutorial `_. + + +---------------- +Convenções +---------------- + +.. code-block:: bash + + $ indica que comando deve ser executado no terminal do Linux + >>> indica que comando deve ser executado pelo interpretador Python em modo interativo + + +=========== +Instalação +=========== + +Ative seu virtualenv e instale o Django na versão 1.8: + +.. code-block:: bash + + $ pip install "django>=1.8,<1.9" + + +Se tiver alguma duvida, você pode olhar na `documentação `_ como instalar o framework. + + +Para verificar se está tudo certo, abra o interpretador python e verifique a versão do Django: + +.. code-block:: python + + >>> import django + >>> print(django.get_version()) + 1.8.5 + + +Isso é tudo que precisamos para começar =) + + +=============== +django-admin.py +=============== + +o ``django-admin.py`` é um script de linha de comando do Django que nos oferece vários comandos administrativos. + +Existem várias opções, para visualizar todas basta executar: + +.. code-block:: bash + + $ django-admin.py help + + +Alguns parâmetros importantes são ``--pythonpath`` e ``--settings``. + + * Como vamos criar nosso projeto do zero, precisamos informar onde nossos módulos estarão localizados e para isso utilizaremos o parâmetro ``--pythonpath``. + * Precisamos informar ao Django onde encontrar nossas configurações e para isso utilizaremos o parâmetro ``--settings``. + + +======================= +Configurando o ambiente +======================= + +Crie um arquivo chamado ``helloworld.py``: + +.. code-block:: bash + + $ touch helloworld.py + +Criamos nosso arquivo e agora vamos rodar o `ambiente de desenvolvimento `_ do Django :D + +.. code-block:: bash + + $ django-admin.py runserver --pythonpath=. --settings=helloworld + + +Dessa forma, estamos dizendo ao Django que nossos arquivos estão no diretório atual e que nossas configurações estão no arquivo ``heloworld`` (não devemos informar a extensão do arquivo no parâmetro). + +**Puts**, ocorreu um erro! + +.. code-block:: bash + + django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty. + +Atualmente o Django não inicia sem a `SECRET_KEY `_ configurada. Precisamos adiciona-la ao nosso arquivo. + +Abra o arquivo ``helloworld.py`` e insira uma SECRET_KEY qualquer: + +.. code-block:: python + + SECRET_KEY='helloworld' + + +Para ambiente de teste não existe nenhum problema em deixar nossa SECRET_KEY com esse valor, porém para o ambiente de produção é necessário que seja um valor randômico. A SECRET_KEY é utilizada em diversas partes do Django para criar hashes e encriptar chaves. Por esse fato, você NUNCA DEVE deixar pública o valor de SECRET_KEY utilizado em ambientes de produção. +Mais informações `aqui `_. + +Executando novamente nosso ambiente, teremos o seguinte erro: + +.. code-block:: bash + + $ django-admin.py runserver --pythonpath=. --settings=helloworld + + CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False. + +Como estamos em ambiente de desenvolvimento, precisamos utilizar o ``DEBUG`` como ``True``, somente para produção que ele será desativado. + +Nosso arquivo ``helloworld.py`` agora está assim: + +.. code-block:: python + + SECRET_KEY='helloworld' + DEBUG = True + +Com isso já é possível subir o ambiente de desenvolvimento. + +.. code-block:: bash + + $ django-admin.py runserver --pythonpath=. --settings=helloworld + + Performing system checks... + + System check identified some issues: + + WARNINGS: + ?: (1_7.W001) MIDDLEWARE_CLASSES is not set. + HINT: Django 1.7 changed the global defaults for the MIDDLEWARE_CLASSES. django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.messages.middleware.MessageMiddleware were removed from the defaults. If your project needs these middleware then you should configure this setting. + + System check identified 1 issue (0 silenced). + September 26, 2015 - 08:50:09 + Django version 1.8.5, using settings 'helloworld' + Starting development server at http://127.0.0.1:8000/ + Quit the server with CONTROL-C. + + +Pronto! Nosso ambiente já está rodando na porta 8000, abra seu navegador e digite ``http://127.0.0.1:8000/``. + +============ +Hello World +============ + + +Ok, nosso ambiente está rodando, porém ainda temos erros. O que aconteceu? + +Se você visualizar no terminal onde o ambiente está sendo executado, verá a seguinte mensagem: + +.. code-block:: bash + + AttributeError: 'Settings' object has no attribute 'ROOT_URLCONF' + + +Para encontrar as *views* que serão renderizadas no projeto, o Django procura primeiro as configurações no +arquivo apontado pelo ``ROOT_URLCONF``. + +* uma view é uma função responsável por retornar algo para ser renderizado no browser, pode ser um html, um arquivo, um json e etc. + + +Como toda nossa aplicação ficará por enquanto no arquivo ``helloworld.py``, vamos apontar nosso ``ROOT_URLCONF`` para ele. + +Abra o arquivo ``helloworld.py`` e insira o seguinte código: + +.. code-block:: python + + SECRET_KEY = 'helloworld' + DEBUG = True + ROOT_URLCONF = __name__ + +Estamos dizendo ao Django que nossas `urls` estão nesse arquivo, para reconhecer as urls, o django procura +pela variável ``urlpatterns``. + +Logo, nosso arquivo deve ficar assim: + +.. code-block:: python + + + SECRET_KEY = 'helloworld' + DEBUG = True + ROOT_URLCONF = __name__ + + urlpatterns = [] + + +Agora, se abrirmos nosso navegador no endereço ``http://127.0.0.1:8000/`` já recebemos a página de bem vindo do Django =DDD + + +.. image:: images/lucasmagnum/itworked.png + :alt: itworked + + +--------------------- +Nossa primeira view +--------------------- + +Agora sim, tudo está pronto para criarmos nossa primeira view! + +Vamos criar nossa view chamada ``hello_world``, toda view recebe como primeiro o ``request``, +e precisa retornar alguma resposta para o navegador, vamos retornar um ``HttpResponse`` com o texto +*Django na prática - Hello World!* + +Modifique seu ``helloworld.py`` para que fique assim: + +.. code-block:: python + + from django.http import HttpResponse + + + SECRET_KEY = 'helloworld' + DEBUG = True + ROOT_URLCONF = __name__ + + def hello_world(request): + return HttpResponse('Django na prática - Hello World!') + + urlpatterns = [] + + +Pronto! Temos nossa view criada, porém ainda não conseguimos acessá-la. +Precisamos dizer ao framework como essa view pode ser encontrada e para qual ``url`` ela deve responder. + +Façamos dessa forma: + +.. code-block:: python + + from django.conf.urls import url + from django.http import HttpResponse + + + SECRET_KEY = 'helloworld' + DEBUG = True + ROOT_URLCONF = __name__ + + def hello_world(request): + return HttpResponse('Django na prática - Hello World!') + + urlpatterns = [ + url(http://github.com/r'%5E),%20hello_world) + ] + +Dentro do ``urlpatterns`` nós informamos quais são as urls disponíveis no nosso projeto. +Fazemos isso usando utilizado uma expressão regular associada à uma função, que no nosso caso é o ``hello_world``. + +Agora, se abrirmos o navegador, iremos nos deparar com o seguinte resultado: + +.. image:: images/lucasmagnum/helloworld.png + :alt: hello world + + +Por hoje é isso!!! Guarde o arquivo criado hoje, pois ele será utilizado nas próximas aulas! + +Até a próxima =) diff --git a/content/django_releases_em_10_minutos.rst b/content/django_releases_em_10_minutos.rst new file mode 100644 index 000000000..7650ca630 --- /dev/null +++ b/content/django_releases_em_10_minutos.rst @@ -0,0 +1,118 @@ +Django - 3 anos em 10 minutos +############################################# + +:date: 2015-04-06 11:55 +:tags: python, django +:category: Python +:slug: django-overview-10-minutos +:author: Lucas Magnum +:email: lucasmagnumlopes@gmail.com +:github: lucasmagnum +:linkedin: lucasmagnum + + +Em março de 2012 foi lançada a versão 1.4 e por aqui que nossa jornada começa, o objetivo não é entrar em detalhes em todas as features que foram implementadas e sim um `overview` sobre as que considero mais importantes para nosso dia a dia. + + +Versão `1.4 `_ (Há 3 anos) +-------------------------------------------------------------------------------- + +Lançada em Março de 2012, suporta Python 2.5 >= 2.7. + +Foi a primeira versão LTS (long-term support), ou seja, ela recebeu correções e atualizações de segurança por pelo menos 3 anos após à data de lançamento. + +É a primeira versão a permitir o uso de `custom project template `_ e tornar o arquivo `manage.py` *o* que conhecemos hoje. + +Foi a primeira versão a suportar o `testes `_ feitos com frameworks que utilizam o browser como o `Selenium `_. + + +Algumas outras coisas legais foram: + + * `bulk_create `_ para criar vários objetos de uma só vez. + * `prefetch_related `_ para realizar `joins` em tabelas que possuem relações `many-to-many`. + * `reverse_lazy `_ que permite fazer `reverse` antes das configurações de URL serem carregadas. + + +Versão `1.5 `_ (Há 2 anos) +-------------------------------------------------------------------------------- + +Lançada em Fevereiro de 2013, suporta Python 2.6.5 >= 2.7 (Python 3 - Experimental). + +A versão 1.5 ficou bem conhecida pela reformulação na aplicação de autenticação, é possível `customizar um usuário `_ conforme nossas necessidades. + +É possivel também passar os campos que você `gostaria de atualizar `_ quando for salvar um objeto. + +Algumas outras coisas legais foram: + + * `verbatim `_ template tag para casos onde você deseja ignorar a sintaxe do template. + * `novos tutoriais `_ foram inseridos para ajudar iniciantes e existe também uma seção `Tutoriais Avançados` + + +Versão `1.6 `_ (Há 1 ano e meio) +-------------------------------------------------------------------------------------- + +Lançada em Novembro de 2013, suporta Python 2.6.5 >= 3.3. (Python 3!!!!) + + +A versão 1.6 alterou um pouco do `layout` padrão dos projetos que estávamos acostumados, para nossa alegria o `admin.py` é adicionado por padrão na aplicação :) + +Transações possuem `autocommit` habilitado por padrão! + +Os testes são localizados em qualquer arquivo que possua o nome começando com **test_** (Antigamente os testes só eram encontrados nos arquivos models.py e tests.py) + +Algumas outras coisas legais foram: + + * `check `_ comando para validar se suas configurações estão compatíveis com a versão do django. + + +Versão `1.7 `_ (Há 8 meses) +--------------------------------------------------------------------------------- + +Lançada em Setembro de 2014, suporta Python 2.7 >= 3.4. + +Um novo conceito de `migrações `_ foi implementado, até o momento a maioria utilizava o `South `_, nessa versão tudo é `built-in`. + +Para criar uma aplicação não é necessário mais conter o arquivo `models.py`. + +O `conceito de aplicação `_ foi atualizado e existem vários novas maneiras de se customizar seus dados :) + +O comando `check` foi evoluído e agora realiza uma varredura quase completa no seu código para identificar possíveis problemas. + + +Versão `1.8 `_ +-------------------------------------------------------------------- + +Lançada em Abril de 2015, a versão 1.8 introduz várias features legais! + +É a segunda versão LTS (long-term support), ou seja, vai receber correções e atualizações de segurança por pelo menos 3 anos após à data de lançamento. + +Agora há a possibilidade de utilizar `várias linguagens(engines) `_ de templates, ou seja, você pode usar simuntaneamente (não no mesmo arquivo) a `Django Template Language `_ ou `Jinja2 `_. Há uma API padronizada para quem quiser adicionar suporte para outras linguagens de template no futuro. Há um `guia de migração `_ . + +Novos campos foram introduzidos como `UUIDField `_ e `DurationField `_ e ainda tem mais! + +O `Model._meta API `_ foi totalmente refatorado e padronizado. O `Model._meta` API foi incluida no Django 0.96 e serve para obter informações sobre campos do Model, contudo, essa api era considerada de uso privado, e não tinha qualquer documentação ou comentários. Agora com tudo padronizado, abre uma gama de novas opções para criar apps plugaveis que fazem coisas com Models arbitrarios. + +Algumas outras coisas legais foram: + + * O comando de gerenciamento `inspectdb `_ agora suporta fazer engenharia reversa tambem de Database Views (nas versões anteriores, o inspectdb inspecionava somente tabelas, mas não conseguia "ver" as views). + * Adição/Melhoria de `Query Expressions, Conditional Expressions, and Database Functions `_ . Isso adiciona muito mais flexibilidade para fazer pesquisas mais complexas no banco de dados. + + + +Há várias outras ótimas melhorias que omiti, veja o `release note `_ completo. + +O Futuro +---------- + +Versão 1.9 (Com lançamento previsto para Outubro de 2016) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +O Django 1.8 mal foi lançado, e já há algumas novidades que talvez venham no Django 1.9. + +Muito provavelmete, o Django 1.9 vai adicionar os tão esperados `Campos Compostos `_ . Isso vai permitir fazer coisas mais complexas, como ter um campo Dinheiro, que "sabe" como fazer conversões de moeda (ex. Real para Dolar). + +Tambem existe uma expectativa que o tema `django-flat-admin `_ para o `Admin `_ seja integrado no Django 1.9, virando o tema padrão. O `django-flat-admin `_ somente modifica o CSS, nenhuma tag HTML é alterada em relação ao HTML original do Admin, então ele é relativemente compativel (desde que você não tenha incluido customizações no CSS do Admin). Os core developers do Django estão tratando desse assunto `neste tópico `_ + + +Veja o `Roadmap `_ do que vem por ai no Django 1.9 + diff --git a/content/encapsulamento-da-logica-do-algoritmo.md b/content/encapsulamento-da-logica-do-algoritmo.md new file mode 100644 index 000000000..34a5104c0 --- /dev/null +++ b/content/encapsulamento-da-logica-do-algoritmo.md @@ -0,0 +1,178 @@ +Title: Encapsulamento da lógica do algoritmo +Slug: encapsulamento-da-logica-do-algoritmo +Date: 2021-03-02 15:00 +Category: Python +Tags: python, poo +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores, estuda DevOps + +Muitas listas de exercícios de lógica de programação pedem em algum momento que um valor seja lido do teclado, e caso esse valor seja inválido, deve-se avisar, e repetir a leitura até que um valor válido seja informado. Utilizando a ideia de [otimização do algoritmo passo a passo](https://dev.to/acaverna/otimizando-o-algoritmo-passo-a-passo-4co0), começando com uma solução simples, pretendo estudar como reduzir a duplicação de código alterando o algoritmo, encapsulando a lógica em funções, e encapsulando em classes. + +## Exercício + +Um exemplo de exercício que pede esse tipo de validação é a leitura de notas, que devem estar entre 0 e 10. A solução mais simples, consiste em ler um valor, e enquanto esse valor for inválido, dar o aviso e ler outro valor. Exemplo: + +```python +nota = float(input('Digite a nota: ')) +while nota < 0 or nota > 10: + print('Nota inválida') + nota = float(input('Digite a nota: ')) +``` + +Esse algoritmo funciona, porém existe uma duplicação no código que faz a leitura da nota (uma antes do *loop* e outra dentro). Caso seja necessário uma alteração, como a mudança da nota para um valor inteiro entre 0 e 100, deve-se alterar os dois lugares, e se feito em apenas um lugar, o algoritmo poderia processar valores inválidos. + +### Alterando o algoritmo + +Visando remover a repetição de código, é possível unificar a leitura do valor dentro do *loop*, uma vez que é necessário repetir essa instrução até que o valor válido seja obtido. Exemplo: + +```python +while True: + nota = float(input('Digite a nota: ')) + if 0 <= nota <= 10: + break + print('Nota inválida!') +``` + +Dessa forma, não existe mais a repetição de código. A condição de parada, que antes verificava se o valor era inválido (o que pode ter uma leitura não tão intuitiva), agora verifica se é um valor válido (que é geralmente é mais fácil de ler e escrever a condição). E a ordem dos comandos dentro do *loop*, que agora estão em uma ordem que facilita a leitura, visto que no algoritmo anterior era necessário tem em mente o que era executado antes do *loop*. + +Porém esses algoritmos validam apenas o valor lido, apresentando erro caso seja informado um valor com formato inválido, como letras em vez de números. Isso pode ser resolvido tratando as exceções lançadas. Exemplo: + +```python +while True: + try: + nota = float(input('Digite a nota: ')) + if 0 <= nota <= 10: + break + except ValueError: + ... + print('Nota inválida!') +``` + +### Encapsulamento da lógica em função + +Caso fosse necessário ler várias notas, com os algoritmos apresentados até então, seria necessário repetir todo esse trecho de código, ou utilizá-lo dentro de uma estrutura de repetição. Para facilitar sua reutilização, evitando a duplicação de código, é possível encapsular esse algoritmo dentro de uma função. Exemplo: + +```python +def nota_input(prompt): + while True: + try: + nota = float(input(prompt)) + if 0 <= nota <= 10: + break + except ValueError: + ... + print('Nota inválida!') + return nota + + +nota1 = nota_input('Digite a primeira nota: ') +nota2 = nota_input('Digite a segunda nota: ') +``` + +### Encapsulamento da lógica em classes + +Em vez de encapsular essa lógica em uma função, é possível encapsulá-la em uma classe, o que permitiria separar cada etapa do algoritmo em métodos, assim como ter um método responsável por controlar qual etapa deveria ser chamada em qual momento. Exemplo: + +```python +class ValidaNotaInput: + mensagem_valor_invalido = 'Nota inválida!' + + def ler_entrada(self, prompt): + return input(prompt) + + def transformar_entrada(self, entrada): + return float(entrada) + + def validar_nota(self, nota): + return 0 <= nota <= 10 + + def __call__(self, prompt): + while True: + try: + nota = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_nota(nota): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return nota + + +nota_input = ValidaNotaInput() + + +nota = nota_input('Digite a nota: ') +``` + +Vale observar que o método `__call__` permite que o objeto criado a partir dessa classe seja chamado como se fosse uma função. Nesse caso ele é o responsável por chamar cada etapa do algoritmo, como: `ler_entrada` que é responsável por ler o que foi digitado no teclado, `transformar_entrada` que é responsável por converter o texto lido para o tipo desejado (converter de `str` para `float`), e `validar_nota` que é responsável por dizer se o valor é válido ou não. Vale observar que ao dividir o algoritmo em métodos diferentes, seu código principal virou uma espécie de código comentado, descrevendo o que está sendo feito e onde está sendo feito. + +Outra vantagem de encapsular a lógica em classe, em vez de uma função, é a possibilidade de generalizá-la. Se fosse necessário validar outro tipo de entrada, encapsulando em uma função, seria necessário criar outra função repetindo todo o algoritmo, alterando apenas a parte referente a transformação do valor lido, e validação, o que gera uma espécie de repetição de código. Ao encapsular em classes, é possível se aproveitar dos mecanismos de herança para evitar essa repetição. Exemplo: + +```python +class ValidaInput: + mensagem_valor_invalido = 'Valor inválido!' + + def ler_entrada(self, prompt): + return input(prompt) + + def transformar_entrada(self, entrada): + raise NotImplementedError + + def validar_valor(self, valor): + raise NotImplementedError + + def __call__(self, prompt): + while True: + try: + valor = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_valor(valor): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return valor + + +class ValidaNomeInput(ValidaInput): + mensagem_valor_invalido = 'Nome inválido!' + + def transformar_entrada(self, entrada): + return entrada.strip().title() + + def validar_valor(self, valor): + return valor != '' + + +class ValidaNotaInput(ValidaInput): + mensagem_valor_invalido = 'Nota inválida!' + + def transformar_entrada(self, entrada): + return float(entrada) + + def validar_valor(self, valor): + return 0 <= valor <= 10 + + +nome_input = ValidaNomeInput() +nota_input = ValidaNotaInput() + + +nome = nome_input('Digite o nome: ') +nota = nota_input('Digite a nota: ') +``` + +Dessa forma, é possível reutilizar o código já existente para criar outras validações, sendo necessário implementar apenas como converter a `str` lida do teclado para o tipo desejado, e como esse valor deve ser validado. Não é necessário entender e repetir a lógica de ler o valor, validá-lo, imprimir a mensagem de erro, e repetir até que seja informado um valor válido. + +## Considerações + +É possível encapsular a lógica de um algoritmo em funções ou em classes. Embora para fazê-lo em uma classe exija conhecimentos de programação orientada a objetos, o seu reaproveitamento é facilitado, abstraindo toda a complexidade do algoritmo, que pode ser disponibilizado através de uma biblioteca, exigindo apenas a implementações de métodos simples por quem for a utilizar. + +Ainda poderia ser discutido outras formas de fazer essa implementação, como passar funções como parâmetro e a utilização de [corrotinas](https://docs.python.org/pt-br/3/library/asyncio-task.html) no encapsulamento do algoritmo em função, assim como a utilização de [classmethod](https://docs.python.org/pt-br/3/library/functions.html#classmethod), [staticmethod](https://docs.python.org/pt-br/3/library/functions.html#staticmethod) e [ABC](https://docs.python.org/pt-br/3/library/abc.html) no encapsulamento do algoritmo em classes. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/entrevista_henrique_bastos.md b/content/entrevista_henrique_bastos.md new file mode 100644 index 000000000..001a244d9 --- /dev/null +++ b/content/entrevista_henrique_bastos.md @@ -0,0 +1,54 @@ +Title: Entrevista com Henrique Bastos +Slug: entrevista-henrique-bastos +Date: 2015-01-23 23:45 +Tags: python,entrevistas +Author: Matheus Ap Godoy Ribeiro +Email: matheusgodoy@me.com +Github: matheus +Bitbucket: +Site: http://matheus.me +Twitter: matheusdotme +Category: Python,Entrevistas + + +Entrevista com Henrique Bastos +------------------------------ + +A primeira vez que ouvi o [HenriqueBastos](https://twitter.com/henriquebastos) foi no [GrokPodcast](http://www.grokpodcast.com/), foi o que me motivou a pesquisar sobre Python + + + +[Episodio 6 - A linguagem Python - Parte 1](http://www.grokpodcast.com/2010/10/20/episodio-6-a-linguagem-python-parte-1/) + +[Episodio 7 - A linguagem Python - Parte 2](http://www.grokpodcast.com/2010/10/28/episodio-7-a-linguagem-python-parte-2/) + +[Episodio 12 - Django - O framework web para perfeccionistas](http://www.grokpodcast.com/2010/12/01/episodio-12-django-o-framework-web-para-perfeccionistas/) + +Li todos os posts no blog [henriquebastos.net](http://henriquebastos.net/). Mas precisava de algo mais,conhecer mais de perto o Henrique e seus conhecimentos foi entao que ingressei +no curso [Welcome To The Django](http://welcometothedjango.com.br/), o qual RECOMENDO FORTEMENTE, mesmo para quem usa outro framework ou linguagem, as boas práticas de desenvolvimento ensinadas no curso são para a vida toda. + +Atualmente acompanho o Henrique Bastos pelo youtube e no podcast [Curto Circuito](http://www.curtocircuito.cc/). + +Seguem as respostas do Henrique. + +> Muito obrigado Henrique! + +_1. Em que momentos na vida agradeceu por ter escolhido Python e quais os motivos?_ + +Essa pergunta e cabulosa e tem muitas sutilezas nas entrelinhas. + +Minha resposta curta é: NENHUM. + +Não me entenda mal. Eu gosto muitíssimo da linguagem Python e de todo o seu ecossistema. Mas observe que eu só "escolhi Python" porquê gosto, porquê percebo afinidades que me atraem. Esses valores subjetivos são difíceis de demonstrar, mas por sorte temos uma pequena referência no [Zen do Python](http://ideiaemconflito.blogspot.com.br/2012/05/o-zen-de-python.html), por exemplo. De todo modo, eu programo mais em Python do que em outras linguagens, mas estou sempre aprendendo algo novo. + +Se há algo pelo que agradecer, fica meu agradecimento para todas as pessoas que colaboraram direta ou indiretamente para que Python exista. E do meu lado, fica a satisfação de "me ouvir" continuamente e insistir na busca pelo que gosto. + +_2. Qual conselho compartilha com jovens desenvolvedores?_ + +Seja cético, principalmente com as suas certezas. Mas seja passional com as suas preferências, sem esquecer que são apenas _suas_ _preferências_. + +_3. Qual pergunta que não fiz você gostaria de responder e qual e a resposta?_ + +Que dia e hoje? + +Dia 2 de Janeiro de 2014. diff --git a/content/explicit_is_better_than_implicit.md b/content/explicit_is_better_than_implicit.md new file mode 100644 index 000000000..0b45b9587 --- /dev/null +++ b/content/explicit_is_better_than_implicit.md @@ -0,0 +1,108 @@ +Title: Explicit is better than implicit +Date: 2016-04-22 23:00 +Tags: python, zen of python, experências, decisões de desenvolvimento +Category: Experiências +Slug: explicit-is-better-than-implicit +Author: Ivan Neto +About_author: Desenvolvedor Python, esposo, pai, escritor nas horas vagas +Email: ivan.cr.neto@gmail.com +Github: ivancrneto +Twitter: ivancrneto +Linkedin: ivanrocha + +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](https://www.toptal.com/#book-tested-programmers)). + +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: + +``` json +{ + // [ ... ] + "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-- +``` +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: + +``` json +{ + // [ ... ] + "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 +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. diff --git a/content/extraindo_texto_de_imagens_com_python.md b/content/extraindo_texto_de_imagens_com_python.md new file mode 100644 index 000000000..30fe41f79 --- /dev/null +++ b/content/extraindo_texto_de_imagens_com_python.md @@ -0,0 +1,73 @@ +Title: Extraindo Texto de Imagens com Python +Date: 2015-11-22 17:00 +Tags: imagens,ocr,pytesseract,extrair texto +Category: Manipulação de imagens +Slug: extraindo-texto-de-imagens-com-python +Author: André Ramos +Email: andrel.ramos97@gmail.com +Github: andrelramos +About_author: Programador web/desktop/mobile. Apaixonado por tecnologia, programação e python. + +Introdução +----------- + +Já precisou extrair texto de imagens mas não sabia como? aprenda como fazer isso com apenas 3 linhas de código (Por isso amo python!). Antes de começarmos, vamos ver um pouco de teoria. + +### O que é OCR? + +Segundo o Wikipedia, OCR é um acrónimo para o inglês Optical Character Recognition, é uma tecnologia para reconhecer caracteres a partir de um arquivo de imagem ou mapa de bits sejam eles escaneados, escritos a mão, datilografados ou impressos. Dessa forma, através do OCR é possível obter um arquivo de texto editável por um computador. A engine OCR que vamos utilizar é a **Tesseract**, a mesma foi inicialmente desenvolvida nos laboratórios da HP e tem seu projeto hospedado em: [https://github.com/tesseract-ocr/tesseract](https://github.com/tesseract-ocr/tesseract). Texto adaptado de: [https://pt.wikipedia.org/wiki/Reconhecimento_ótico_de_caracteres](https://pt.wikipedia.org/wiki/Reconhecimento_%C3%B3tico_de_caracteres) + +Como descrito acima, já existe uma tecnologia para realizar essa função, então apenas precisamos utilizá-la em nosso script python e assim desenvolvermos o que a imaginação permitir. + +### Instalando Dependências (Ubuntu) + +Primeiro vamos começar pela instalação do Tesseract OCR. Abra o terminal e digite o seguinte comando: + + :::bash + $ sudo apt-get install tesseract-ocr tesseract-ocr-por + +Também precisamos instalar a biblioteca Pillow e suas dependências. Ela será necessária para carregar a imagem para nosso script: + +Ubuntu 12.04/14.04: + + :::bash + $ sudo apt-get install python-dev python3-dev build-essential liblcms1-dev zlib1g-dev libtiff4-dev libjpeg8-dev libfreetype6-dev libwebp-dev + $ sudo -H pip install Pillow + +Ubuntu 15.04/15.10/16.04: + + :::bash + $ sudo apt-get install python-dev python3-dev build-essential liblcms2-dev zlib1g-dev libtiff5-dev libjpeg8-dev libfreetype6-dev libwebp-dev + $ sudo -H pip install Pillow + + +Agora partiremos para a instalação do wrapper que irá permitir a utilização do Tesseract através do python: + + :::bash + $ sudo -H pip install pytesseract + + +### Mão Na Massa! + +Finalmente chegamos a parte prática desse artigo. Como dito anteriormente, são apenas 3 linhas de código, mas antes de começar baixe a seguinte imagem para realizar seus testes: + +![imagem para teste](images/andrelramos/ocr2.png "Imagem Para Teste") + +Agora vamos ao código: + + :::python + + from PIL import Image # Importando o módulo Pillow para abrir a imagem no script + + import pytesseract # Módulo para a utilização da tecnologia OCR + + print( pytesseract.image_to_string( Image.open('nome_da_imagem.jpg') ) ) # Extraindo o texto da imagem + +Simples né? Mas nem sempre o texto sai 100% correto, depende muito da qualidade da imagem e da quantidade de detalhes que a mesma possui, porem existe algumas técnicas usadas para fazer melhorias na imagem diminuindo a chance de erros na hora da extração. + +**Alguns links que podem te ajudar a aproveitar ao maximo da tecnologia OCR:** + +* [http://pt.scribd.com/doc/88203318/Como-escanear-livros-com-qualidade-e-produzir-textos-por-OCR#scribd](http://pt.scribd.com/doc/88203318/Como-escanear-livros-com-qualidade-e-produzir-textos-por-OCR#scribd) + +* [http://profs.if.uff.br/tjpp/blog/entradas/ocr-de-qualidade-no-linux](http://profs.if.uff.br/tjpp/blog/entradas/ocr-de-qualidade-no-linux) + diff --git a/content/fazendo-backup-do-banco-de-dados-no-django.md b/content/fazendo-backup-do-banco-de-dados-no-django.md new file mode 100644 index 000000000..c8de95af4 --- /dev/null +++ b/content/fazendo-backup-do-banco-de-dados-no-django.md @@ -0,0 +1,122 @@ +Title: Fazendo backup do banco de dados no Django +Date: 2020-10-30 10:40 +Tags: Python,Django,backup +Category: Python +Slug: fazendo-backup-do-banco-de-dados-no-django +Author: Jackson Osvaldo +Email: jacksonosvaldo@live.com +Github: JacksonOsvaldo +About_author: Um curioso apaixonado por livros, tecnologia e programação. + +## Apresentação + +Em algum momento, durante o seu processo de desenvolvimento com Django, pode ser que surja a necessidade de criar e restaurar o banco de dados da aplicação. Pensando nisso, resolvi fazer um pequeno tutorial, básico, de como realizar essa operação. + +Nesse tutorial, usaremos o [django-dbbackup](https://github.com/django-dbbackup/django-dbbackup), um pacote desenvolvido especificamente para isso. + +## Configurando nosso ambiente + +Primeiro, partindo do início, vamos criar uma pasta para o nosso projeto e, nela, isolar o nosso ambiente de desenvolvimento usando uma [virtualenv](https://virtualenv.pypa.io/en/latest/index.html): + +```shell +mkdir projeto_db && cd projeto_db #criando a pasta do nosso projeto + +virtualenv -p python3.8 env && source env/bin/activate #criando e ativando a nossa virtualenv +``` + +Depois disso e com o nosso ambiente já ativo, vamos realizar os seguintes procedimentos: + +```shell +pip install -U pip #com isso, atualizamos a verão do pip instalado +``` + +## Instalando as dependências + +Agora, vamos instalar o [Django](https://www.djangoproject.com/) e o pacote que usaremos para fazer nossos backups. + +```shell +pip install Django==3.1.2 #instalando o Django + +pip install django-dbbackup #instalando o django-dbbackup +``` + +## Criando e configurando projeto + +Depois de instaladas nossas dependências, vamos criar o nosso projeto e configurar o nosso pacote nas configurações do Django. + +```shell +django-admin startproject django_db . #dentro da nossa pasta projeto_db, criamos um projeto Django com o nome de django_db. +``` + +Depois de criado nosso projeto, vamos criar e popular o nosso banco de dados. + +```shell +python manage.py migrate #com isso, sincronizamos o estado do banco de dados com o conjunto atual de modelos e migrações. +``` + +Criado nosso banco de dados, vamos criar um superusuário para podemos o painel admin do nosso projeto. + +```shell +python manage.py createsuperuser +``` + +Perfeito. Já temos tudo que precisamos para executar nosso projeto. Para execução dele, é só fazermos: + +```shell +python manage.py runserver +``` + +Você terá uma imagem assim do seu projeto: + +![](https://jacksonosvaldo.github.io/img/django_db.png) + +## Configurando o django-dbbackup + +Dentro do seu projeto, vamos acessar o arquivo settings.py, como expresso abaixo: + +```shell +django_db/ +├── settings.py +``` + +Dentro desse arquivos iremos, primeiro, adiconar o django-dbbackup às apps do projeto: + +```python +INSTALLED_APPS = ( + ... + 'dbbackup', # adicionando django-dbbackup +) +``` + +Depois de adicionado às apps, vamos dizer para o Django o que vamos salvar no backup e, depois, indicar a pasta para onde será encaminhado esse arquivo. Essa inserção deve ou pode ser feita no final do arquivo _settings.py_: + +```python +DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage' #o que salvar +DBBACKUP_STORAGE_OPTIONS = {'location': 'backups/'} # onde salvar +``` + +Percebam que dissemos para o Django salvar o backup na pasta _backups_, mas essa pasta ainda não existe no nosso projeto. Por isso, precisamos criá-la [fora da pasta do projeto]: + +```shell +mkdir backups +``` + +## Criando e restaurando nosso backup + +Já temos tudo pronto. Agora, vamos criar o nosso primeiro backup: + +```shell +python manage.py dbbackup +``` + +Depois de exetudado, será criado um arquivo -- no nosso exemplo, esse arquivo terá uma extensão .dump --, salvo na pasta _backups_. Esse arquivo contem todo backup do nosso banco de dados. + +Para recuperarmos nosso banco, vamos supor que migramos nosso sistema de um servidor antigo para um novo e, por algum motivo, nossa base de dados foi corrompida, inviabilizando seu uso. Ou seja, estamos com o sistema/projeto sem banco de dados -- ou seja, exlua ou mova a a sua base dados .sqlite3 para que esse exemplo seja útil --, mas temos os backups. Com isso, vamos restaurar o banco: + +```shell +python manage.py dbrestore +``` + +Prontinho, restauramos nosso banco de dados. O interessante do django-dbbackup, dentre outras coisas, é que ele gera os backups com datas e horários específicos, facilitando o processo de recuperação das informações mais recentes. + +Por hoje é isso, pessoal. Até a próxima. ;) diff --git a/content/fork_clone_push_pull-request.rst b/content/fork_clone_push_pull-request.rst index 1e6566a24..b2e34cb0d 100644 --- a/content/fork_clone_push_pull-request.rst +++ b/content/fork_clone_push_pull-request.rst @@ -54,7 +54,7 @@ Arquivo .gitignore: O que é o arquivo .gitignore: http://pt-br.gitready.com/iniciante/2009/01/19/ignoring-files.html -Gerador de arquivo .gitignore: http://www.gitignore.io/ +Gerador de arquivo .gitignore: https://www.toptal.com/developers/gitignore Exemplos de arquivo .gitignore para diversas linguagens: https://github.com/github/gitignore diff --git a/content/funcao-inplace-ou-copia-de-valor.md b/content/funcao-inplace-ou-copia-de-valor.md new file mode 100644 index 000000000..fda5844c7 --- /dev/null +++ b/content/funcao-inplace-ou-copia-de-valor.md @@ -0,0 +1,177 @@ +Title: Funções in place ou cópia de valor +Slug: funcao-inplace-ou-copia-de-valor +Date: 2021-03-29 12:00 +Category: Python +Tags: python, funções +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Eventualmente observo dificuldades de algumas pessoas em usar corretamente alguma função, seja porque a função deveria ser executada isoladamente, e utilizado a própria variável que foi passada como argumento posteriormente, seja porque deveria se atribuir o retorno da função a alguma variável, e utilizar essa nova variável. No Python, essa diferença pode ser observada nos métodos das listas `sort` e `reverse` para as funções `sorted` e `reversed`, que são implementadas com padrões diferentes, *in place* e cópia de valor respectivamente. Assim pretendo discutir esses dois padrões de funções, comentando qual a diferença e o melhor caso de aplicação de cada padrão. + +## Função de exemplo + +Para demonstrar como esses padrões funcionam, será implementado uma função que recebe uma lista e calcula o dobro dos valores dessa lista. Exemplo: + +```python +entrada = [5, 2, 8, 6, 4] + +# Execução da função + +resultado = [10, 4, 16, 12, 8] +``` + +### Função com in place + +A ideia do padrão *in place* é alterar a própria variável recebida pela função (ou o próprio objeto, caso esteja lidando com orientação a objetos). Neste caso, bastaria calcular o dobro do valor de cada posição da lista, e sobrescrever a posição com seu resultado. Exemplo: + +```python +from typing import List + + +def dobro_inplace(lista: List[int]) -> None: + for i in range(len(lista)): + lista[i] = 2 * lista[i] + + +valores = [5, 2, 8, 6, 4] +retorno = dobro_inplace(valores) + +print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}') +print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}') +``` + +Resultado da execução: + +``` +Variável: valores | Tipo: | Valor: [10, 4, 16, 12, 8] +Variável: retorno | Tipo: | Valor: None +``` + +Com essa execução é possível observar que os valores da lista foram alterados, e que o retorno da função é nulo (`None`), ou seja, a função alterou a própria lista passada como argumento. Outro ponto importante a ser observado é a assinatura da função (tipo dos argumentos e do retorno da função), que recebe uma lista de inteiros e não tem retorno ou é nulo (`None`). Dessa forma embora seja possível chamar essa função diretamente quando está se informando os argumentos de outra função, como `print(dobro_inplace(valores))`, a função `print` receberia `None` e não a lista como argumento. + +### Função com cópia de valor + +A ideia do padrão cópia de valor é criar uma cópia do valor passado como argumento e retornar essa cópia, sem alterar a variável recebida (ou criando um novo objeto, no caso de orientação a objetos). Neste caso, é necessário criar uma nova lista e adicionar nela os valores calculados. Exemplo: + +```python +from typing import List + + +def dobro_copia(lista: List[int]) -> List[int]: + nova_lista = [] + + for i in range(len(lista)): + nova_lista.append(2 * lista[i]) + + return nova_lista + + +valores = [5, 2, 8, 6, 4] +retorno = dobro_copia(valores) + +print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}') +print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}') +``` + +Resultado da execução: + +``` +Variável: valores | Tipo: | Valor: [5, 2, 8, 6, 4] +Variável: retorno | Tipo: | Valor: [10, 4, 16, 12, 8] +``` + +Com essa execução é possível observar que a variável `valores` continua com os valores que tinha antes da execução da função, e a variável retorno apresenta uma lista com os dobros, ou seja, a função não altera a lista passada como argumento e retorna uma nova lista com os valores calculados. Observado a assinatura da função, ela recebe uma lista de inteiros e retorna uma lista de inteiros. Isso permite chamar essa função diretamente nos argumentos para outra função, como `print(dobro_copia(valores))`, nesse caso a função `print` receberia a lista de dobros como argumento. Porém caso o retorno da função não seja armazenado, parecerá que a função não fez nada, ou não funcionou. Então em alguns casos, quando o valor anterior não é mais necessário, pode-se reatribuir o retorno da função a própria variável passada como argumento: + +```python +valores = dobro_copia(valores) +``` + +### Função híbrida + +Ainda é possível mesclar os dois padrões de função, alterando o valor passado e retornando-o. Exemplo: + +```python +from typing import List + + +def dobro_hibrido(lista: List[int]) -> List[int]: + for i in range(len(lista)): + lista[i] = 2 * lista[i] + + return lista + + +valores = [5, 2, 8, 6, 4] +retorno = dobro_hibrido(valores) + +print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}') +print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}') +``` + +Resultado da execução: + +``` +Variável: valores | Tipo: | Valor: [10, 4, 16, 12, 8] +Variável: retorno | Tipo: | Valor: [10, 4, 16, 12, 8] +``` + +Nesse caso, pode-se apenas chamar a função, como também utilizá-la nos argumentos de outras funções. Porém para se ter os valores originais, deve-se fazer uma cópia manualmente antes de executar a função. + +## Exemplo na biblioteca padrão + +Na biblioteca padrão do Python, existem os métodos `sort` e `reverse` que seguem o padrão *in place*, e as funções `sorted` e `reversed` que seguem o padrão cópia de valor, podendo ser utilizados para ordenar e inverter os valores de uma lista, por exemplo. Quando não é mais necessário uma cópia da lista com a ordem original, é preferível utilizar funções *in place*, que alteram a própria lista, e como não criam uma cópia da lista, utilizam menos memória. Exemplo: + +```python +valores = [5, 2, 8, 6, 4] +valores.sort() +valores.reverse() +print(valores) +``` + +Se for necessário manter uma cópia da lista inalterada, deve-se optar pelas funções de cópia de valor. Exemplo: + +```python +valores = [5, 2, 8, 6, 4] +novos_valores = reversed(sorted(valores)) +print(novos_valores) +``` + +Porém esse exemplo cria duas cópias da lista, uma em cada função. Para criar apenas uma cópia, pode-se misturar funções *in place* com cópia de valor. Exemplo: + +```python +valores = [5, 2, 8, 6, 4] +novos_valores = sorted(valores) +novos_valores.reverse() +print(novos_valores) +``` + +Também vale observar que algumas utilizações dessas funções podem dar a impressão de que elas não funcionaram, como: + +```python +valores = [5, 2, 8, 6, 4] + +sorted(valores) +print(valores) # Imprime a lista original, e não a ordenada + +print(valores.sort()) # Imprime None e não a lista +``` + +## Considerações + +Nem sempre é possível utilizar o padrão desejado, *strings* no Python (`str`) são imutáveis, logo todas as funções que manipulam elas seguiram o padrão cópia de valor, e para outros tipos, pode ocorrer de só existir funções *in place*, sendo necessário fazer uma cópia manualmente antes de chamar a função, caso necessário. Para saber qual padrão a função implementa, é necessário consultar sua documentação, ou verificando sua assinatura, embora ainda possa existir uma dúvida entre cópia de valor e híbrida, visto que a assinatura dos dois padrões são iguais. + +Os exemplos aqui dados são didáticos. Caso deseja-se ordenar de forma reversa, tanto o método `sort`, quanto a função `sorted` podem receber como argumento `reverse=True`, e assim já fazer a ordenação reversa. Assim como é possível criar uma nova lista já com os valores, sem precisar adicionar manualmente item por item, como os exemplos: + +```python +valores = [5, 2, 8, 6, 4] +partes_dos_valores = valores[2:] +novos_valores = [2 * valor for valor in valores] +``` + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/gerando-relatorios-de-teste-com-coveralls.md b/content/gerando-relatorios-de-teste-com-coveralls.md new file mode 100644 index 000000000..569baf09a --- /dev/null +++ b/content/gerando-relatorios-de-teste-com-coveralls.md @@ -0,0 +1,146 @@ +Title: Relatórios de testes com Coveralls +Slug: gerando-relatorios-de-testes-com-coveralls +Date: 2016-06-03 11:28:55 +Category: Python +Tags: python, coveralls, coverage, relatório, test +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io/ + +Na [terceira parte](https://mstuttgart.github.io/2016/04/29/2016-04-29-python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4/) do tutorial sobre *unittest*, vimos como utilizar o serviço [Coveralls](https://coveralls.io/) para gerar relatórios sobre o testes do nosso projeto. Entretanto, uma "desvantagem" do serviço é que o processo de análise é iniciado apenas quando executarmos um *push* ou um *pull request*. Sendo assim, não seria interessante termos a liberdade de executar esses testes localmente? + +Felizmente, os desenvolvedores do [Coveralls](https://coveralls.io/) pensaram nisso e criaram um conjunto de comandos que nos permite executá-lo pelo terminal. + +### Instalação + +Então, antes de iniciarmos, vamos a instalação do módulo, que pode ser feito pelo comando a seguir: + +```bash +pip install coveralls +``` + +Quando você instala o módulo, um *script* de linha de comando chamado `coverage` é adicionado ao diretório de *scripts* python no seu sistema. Para suportar diferentes versões do Python, o módulo vem com um conjunto de *scripts*. Então, para a versão 2.7 do Python, você pode utilizar o comando `coverage` ou `coverage2`. Para a versão 3, utilize `coverage3`. + +### Gerando relatórios + +O comando usado para obtermos um relatório sobre os testes do nosso projeto é simples. No diretório do projeto, basta executar: + +```bash +coverage run --source=nomedopacote setup.py test +``` +o comando `run` irá coletar dados sobre nosso código fonte. No nosso caso, usaremos o repositorio que criamos para o tutorial anterior: [codigo-avulso-test-tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial). Assim, o comando seria: + +```bash +coverage run --source=codigo_avulso_test_tutorial setup.py test +``` + +Se você executar o comando `ls -la` no terminal, verá que um arquivo chamando `.coverage` foi criado. Esse arquivo contém algumas informações sobre o seu código. Vale alertar que para gerar os relatórios precisamos, obrigatoriamente, executar o comando acima, quando formos gerar o relatórios pela primeira vez ou quando o código sofrer alguma modificação. + +Uma vez que o arquivo `.coverage` foi gerado, execute o seguinte comando: + +```bash +coverage report +``` + +Um relatório com a porcentagem de cobertura de testes (entre outras informações) de cada arquivo de código fonte será exibido no terminal. + +```bash +Name Stmts Miss Cover +---------------------------------------------------------------------- +codigo_avulso_test_tutorial/__init__.py 0 0 100% +codigo_avulso_test_tutorial/circulo.py 9 0 100% +codigo_avulso_test_tutorial/figura_geometrica.py 5 0 100% +codigo_avulso_test_tutorial/quadrado.py 8 0 100% +---------------------------------------------------------------------- +TOTAL 22 0 100% +``` + +As colunas exibidas no relatório possuem informações interessantes. São elas: + +* Stmts: indica o total de trechos do código que, segundo o Coveralls, devem ser testados. +* Miss: coluna que indica quantos trechos do código ainda não estão sob testes. +* Cover: talvez a coluna mais importante, ela indica a porcentagem de cobertura de testes do arquivo fonte. + +Em `TOTAL` temos a cobertura total de testes do nosso projeto. Nesse projeto em especial, temos 100% porque o mesmo possui pouco código e os códigos existentes são simples de testar. Entretanto, em projeto mais complexos, nem sempre é possível chegar nessa porcentagem, então vale a pena se focar em escrever testes para as partes mais críticas do seu código e conseguir uma porcentagem perto dos 80%, considerado pelo `Coveralls` como satisfatório. + +#### Gerando relatório em HTML + +Uma opção interessante é gerar o relatório em formato `html` com o comando: + +```bash +coverage html +``` + +Um diretório chamado `htmlcov` será criado no diretório do projeto. Dentro desse diretório existe um arquivo de nome `index.html`, que pode ser aberto no navegador. + +Para o Google Chrome, usamos: + +```bash +google-chrome htmlcov/index.html +``` +ou com o Firefox + +```bash +firefox htmlcov/index.html +``` + +Abaixo temos o `index.html` aberto. Nele podemos ver a lista dos arquivos que estão cobertos pelo `Coveralls`. + +
+![](images/mstuttgart/snapshot_41.png) +
+ +Vamos analisar os dados do arquivo `circulo.py`. Assim, temos as seguintes colunas: + +* `statements`: indica o total de trechos do código que, segundo o Coveralls, devem ser testadas. No caso do arquivo `circulo.py`, o valor da coluna é 9, indicando que existem 9 trechos do código quem devem estar sob teste. +* `missing`: coluna que indica quantos trechos do código ainda não estão sob testes. +* `excluded`: coluna que indica quantos trechos do código foram ignorados pelo Coveralls. Algumas vezes pode ser necessário excluir alguns trechos de código do relatório devido ao tipo de código nele contido ou porque você simplesmente não deseja que aquele trecho seja incluido no relatorio. Isso é feito através de um arquivo de configuração, visto mais adiante. +* `coverage`: indica a porcentagem de cobertura de testes do nosso código. + +Na imagem abaixo, logo após clicarmos em `codigo_avulso_test_tutorial/circulo.py`, podemos ver os pontos do código que devem ser testados. + +
+![](images/mstuttgart/snapshot_42.png) +
+ +Ao clicarmos nos três botões no cabeçalho da página: + +
+![](images/mstuttgart/snapshot_43.png) +
+ +A página irá destacar, respectivamente, os trechos cobertos por testes, trechos sem testes ou que foram excluídos do `Coveralls`. + +#### Gerando relatório em XML +Os relatórios em XML podem ser facilmente gerados por: + +```bash +coverage xml +``` +Um arquivo chamado `coverage.xml` será criado. + +#### Criando o arquivo coveragerc + +O arquivo `.coveragesrc` é usado para determinar parâmetros de funcionamento do comando `coverage`. Nele podemos incluir e excluir aquivos da analise do `Coveralls` entre outras configurações. Abaixo temos um exemplo de arquivo de configuração. + +``` +[run] +source = codigo_avulso_test_tutorial +omit = + codigo_avulso_test_tutorial/__init__.py + codigo_avulso_test_tutorial/main.py +``` + +Na configuração acima, vamos omitir da análise o arquivo `__init__.py` e um arquivo `main.py`. Indicamos o *source* que é onde o `Coveralls` deve executar a análise. + +O arquivo de configuração ainda pode receber várias informações. Você pode ver mais delas [aqui](http://coverage.readthedocs.io/en/latest/source.html#source). + +### Conclusão + +Neste tutorial vimos um pouco mais sobre o `Coveralls`. Evitei colocar as informações deste tutorial nos tutoriais anteriores a fim de simplificá-los. Você pode aprender mais sobre o módulo consultando sua documentação [aqui](http://coverage.readthedocs.io/en/latest/index.html). + +É isso pessoal, obrigado pela leitura e até o próximo tutorial. + +**Publicado originalmente:** [gerando-relatorios-de-testes-com-coveralls](https://mstuttgart.github.io/2016/05/18/2016-05-18-gerando-relatorios-de-teste-com-coveralls/) diff --git a/content/gerenciando-banco-dados-sqlite3-python-parte1.rst b/content/gerenciando-banco-dados-sqlite3-python-parte1.rst index 1fe72bb1c..0909a4e26 100644 --- a/content/gerenciando-banco-dados-sqlite3-python-parte1.rst +++ b/content/gerenciando-banco-dados-sqlite3-python-parte1.rst @@ -12,7 +12,7 @@ Gerenciando banco de dados SQLite3 com Python - Parte 1 Eu separei este post em duas partes: a **Parte 1** é bem elementar e objetiva, visando apresentar o básico sobre a realização do CRUD num banco de dados SQLite3 em Python usando o terminal. -A **Parte 2**, num nível intermediário, usa classes e métodos mais elaborados para gerenciar o CRUD, e algumas coisinhas a mais. +A `parte 2 `_ , num nível intermediário, usa classes e métodos mais elaborados para gerenciar o CRUD, e algumas coisinhas a mais. Nota: Para entender o uso de classes e métodos leia o post `Introdução a Classes e Métodos em Python `_. E para entender os comandos SQL e a manipulação de registros no SQLite3 leia `Guia rápido de comandos SQLite3 `_. @@ -44,7 +44,7 @@ Para os exemplos considere a tabela ``clientes`` e seus campos: Obs: O campo ``bloqueado`` nós vamos inserir depois com o comando ``ALTER TABLE``. -Veja os exemplos em `github `_. +Veja os exemplos em `github `_. Como mencionado antes, esta parte será **básica e objetiva**. A intenção é realizar o CRUD da forma mais simples e objetiva possível. @@ -635,12 +635,12 @@ Para executar digite no terminal: Com o último comando você verá que os dados estão lá. São e salvo!!! -Leia a continuação deste artigo em *Gerenciando banco de dados SQLite3 com Python - Parte 2*. +Leia a continuação deste artigo em `Gerenciando banco de dados SQLite3 com Python - Parte 2 `_. Exemplos -------- -Veja os exemplos em `github `_. +Veja os exemplos em `github `_. Referências ----------- diff --git a/content/gerenciando-banco-dados-sqlite3-python-parte2.rst b/content/gerenciando-banco-dados-sqlite3-python-parte2.rst new file mode 100644 index 000000000..8ec7d9eb7 --- /dev/null +++ b/content/gerenciando-banco-dados-sqlite3-python-parte2.rst @@ -0,0 +1,1235 @@ +Gerenciando banco de dados SQLite3 com Python - Parte 2 +======================================================= + +:date: 2014-11-23 23:59 +:tags: Python, Banco de dados +:category: Python, Banco de dados +:slug: gerenciando-banco-dados-sqlite3-python-parte2 +:author: Regis da Silva +:email: regis.santos.100@gmail.com +:github: rg3915 +:summary: Esta é a continuação do artigo Gerenciando banco de dados SQLite3 com Python - Parte 1. + +Esta é a continuação do artigo `Gerenciando banco de dados SQLite3 com Python - Parte 1 `_. Na 1ª parte nós vimos como realizar o CRUD num banco de dados SQLite3 usando o Python, mas cada tarefa foi feita num arquivo ``.py`` separado. A intenção agora é utilizar um **único arquivo** e, usando classes e métodos realizar as mesmas tarefas, só que de uma forma mais sofisticada. + + Também fiz uma série de videos, assista a primeira aula abaixo ou acesse todas as aulas no `YouTube `_. + +.. image:: images/regisdasilva/youtube_logo.png + :alt: youtube_logo.png + :target: https://www.youtube.com/watch?v=Qe3N7jiGZAc&list=PLsGCdfxkV9upVUtH0zsJ2f4WhQvJrZsVb + + +.. youtube:: Qe3N7jiGZAc + +Vou repetir a tabela ``clientes`` apenas por comodidade: + ++-----------+-----------------+-----------+ +| Campo | Tipo | Requerido | ++===========+=================+===========+ +| id | inteiro | sim | ++-----------+-----------------+-----------+ +| nome | texto | sim | ++-----------+-----------------+-----------+ +| idade | inteiro | não | ++-----------+-----------------+-----------+ +| cpf | texto (11) | sim | ++-----------+-----------------+-----------+ +| email | texto | sim | ++-----------+-----------------+-----------+ +| fone | texto | não | ++-----------+-----------------+-----------+ +| cidade | texto | não | ++-----------+-----------------+-----------+ +| uf | texto (2) | sim | ++-----------+-----------------+-----------+ +| criado_em | data | sim | ++-----------+-----------------+-----------+ +| bloqueado | boleano | não | ++-----------+-----------------+-----------+ + +Obs: O campo ``bloqueado`` nós vamos inserir depois com o comando ``ALTER TABLE``. + + PS: *Considere a sintaxe para Python 3*. Mas o programa roda em python2 também. + +Veja os exemplos em `github `_. + +`Preparando o terreno`_ + +`Configurando um VirtualEnv para Python 3`_ + +`Criando valores randômicos`_ + +`Conectando e desconectando do banco`_ + +`Modo interativo`_ + +`Criando um banco de dados`_ + +`Criando uma tabela`_ + +`Create - Inserindo um registro com comando SQL`_ + +`Inserindo n registros com uma lista de dados`_ + +`Inserindo registros de um arquivo externo`_ + +`Importando dados de um arquivo csv`_ + +`Inserindo um registro com parâmetros de entrada definido pelo usuário`_ + +`Inserindo valores randômicos`_ + +`Read - Lendo os dados`_ + +`Mais SELECT`_ + +`SELECT personalizado`_ + +`Update - Alterando os dados`_ + +`Delete - Deletando os dados`_ + +`Adicionando uma nova coluna`_ + +`Lendo as informações do banco de dados`_ + +`Fazendo backup do banco de dados (exportando dados)`_ + +`Recuperando o banco de dados (importando dados)`_ + +`Conectando-se a outro banco`_ + +`Exemplos`_ + +`Referências`_ + +Preparando o terreno +-------------------- + +Neste artigo eu usei os pacotes `names `_ e `rstr `_ , o primeiro gera nomes randômicos e o segundo gera string e números randômicos. No meu SO estou usando o Python 3.4, mas para não ter problemas com os pacotes eu criei um ambiente virtual. + +**Obs**: *Se você estiver usando Python 3 ou Python 2x não é obrigado a usar virtualenv mas mesmo assim precisará instalar os pacotes names e rstr.* + +Configurando um VirtualEnv para Python 3 +---------------------------------------- + +Não é obrigatório, mas como eu tenho no meu SO o Python 3.4, tive que criar um virtualenv, que se configura da seguinte forma: + +Faça um clone deste repositório + +.. code-block:: bash + + $ git clone https://github.com/rg3915/python-sqlite.git + +Crie o virtualenv com o nome **python-sqlite** + +.. code-block:: bash + + $ virtualenv python-sqlite + +Habilite o python3 + +.. code-block:: bash + + $ virtualenv -p /usr/bin/python3 python-sqlite + +Vá para a pasta + +.. code-block:: bash + + $ cd python-sqlite + +Ative o ambiente + +.. code-block:: bash + + $ source bin/activate + +Seu prompt ficará assim (ou parecido) + +.. code-block:: bash + + (python-sqlite)~/git/python-sqlite$ + +Instale as dependências + +.. code-block:: bash + + $ pip install -r requirements.txt + +Entre na pasta + +.. code-block:: bash + + $ cd intermediario + +Agora vamos diminuir o caminho do prompt + +.. code-block:: bash + + PS1="(`basename \"$VIRTUAL_ENV\"`):/\W$ " + +O prompt vai ficar assim: + +.. code-block:: bash + + (python-sqlite):/intermediario$ + +Pronto! Agora vai começar a brincadeira. + +Criando valores randômicos +-------------------------- + +Antes de mexer no banco de fato vamos criar uns valores randômicos para popular o banco futuramente. + +O arquivo `gen_random_values.py `_ gera idade, cpf, telefone, data e cidade aleatoriamente. Para isso vamos importar algumas bibliotecas. + +.. code-block:: python + + # gen_random_values.py + import random + import rstr + import datetime + +Vamos criar uma função ``gen_age()`` para gerar um número inteiro entre 15 e 99 usando o comando `random.randint(a,b) `_ . + +.. code-block:: python + + def gen_age(): + return random.randint(15, 99) + +A função ``gen_cpf()`` gera uma string com 11 caracteres numéricos. No caso, o primeiro parâmetro são os caracteres que serão sorteados e o segundo é o tamanho da string. + +.. code-block:: python + + def gen_cpf(): + return rstr.rstr('1234567890', 11) + +Agora vamos gerar um telefone com a função ``gen_phone()`` no formato (xx) xxxx-xxxx + +.. code-block:: python + + def gen_phone(): + return '({0}) {1}-{2}'.format( + rstr.rstr('1234567890', 2), + rstr.rstr('1234567890', 4), + rstr.rstr('1234567890', 4)) + +A função ``gen_timestamp()`` gera um *datetime* no formato ``yyyy-mm-dd hh:mm:ss.000000``. Repare no uso do ``random.randint(a,b)`` com um intervalo definido para cada parâmetro. + +Quando usamos o comando `datetime.datetime.now().isoformat() `_ ele retorna a data e hora atual no formato ``yyyy-mm-ddThh:mm:ss.000000``. Para suprimir a letra T usamos o comando ``.isoformat(" ")`` que insere um espaço no lugar da letra T. + +.. code-block:: python + + def gen_timestamp(): + year = random.randint(1980, 2015) + month = random.randint(1, 12) + day = random.randint(1, 28) + hour = random.randint(1, 23) + minute = random.randint(1, 59) + second = random.randint(1, 59) + microsecond = random.randint(1, 999999) + date = datetime.datetime( + year, month, day, hour, minute, second, microsecond).isoformat(" ") + return date + +A função ``gen_city()`` escolhe uma cidade numa lista com o comando `random.choice(seq) `_ (suprimi alguns valores). + +.. code-block:: python + + def gen_city(): + list_city = [ + [u'São Paulo', 'SP'], + [u'Rio de Janeiro', 'RJ'], + [u'Porto Alegre', 'RS'], + [u'Campo Grande', 'MS']] + return random.choice(list_city) + + +Conectando e desconectando do banco +----------------------------------- + +Como mencionado antes, a intenção é criar um único arquivo. Mas, inicialmente, vamos usar um arquivo exclusivo para conexão o qual chamaremos de `connect_db.py `_ , assim teremos um arquivo que pode ser usado para vários testes de conexão com o banco de dados. + +.. code-block:: python + + # connect_db.py + import sqlite3 + + class Connect(object): + + def __init__(self, db_name): + try: + # conectando... + self.conn = sqlite3.connect(db_name) + self.cursor = self.conn.cursor() + # imprimindo nome do banco + print("Banco:", db_name) + # lendo a versão do SQLite + self.cursor.execute('SELECT SQLITE_VERSION()') + self.data = self.cursor.fetchone() + # imprimindo a versão do SQLite + print("SQLite version: %s" % self.data) + except sqlite3.Error: + print("Erro ao abrir banco.") + return False + +Aqui usamos o básico já visto na `parte 1 `_ que são os comandos ``sqlite3.connect()`` e ``cursor()``. Criamos uma classe "genérica" chamada ``Connect()`` que representa o banco de dados. E no inicializador da classe ``__init__`` fazemos a conexão com o banco e imprimimos a versão do SQLite, definido em ``self.cursor.execute('SELECT SQLITE_VERSION()')``. + +O próximo passo é fechar a conexão com o banco: + +.. code-block:: python + + def close_db(self): + if self.conn: + self.conn.close() + print("Conexão fechada.") + +Este método está dentro da classe ``Connect()``, portanto atente-se a **identação**. + +Agora, criamos uma instância da classe acima e chamamos de ``ClientesDb()``, representando um banco chamado *clientes.db*. + +.. code-block:: python + + class ClientesDb(object): + + def __init__(self): + self.db = Connect('clientes.db') + + def close_connection(self): + self.db.close_db() + +Fazendo desta forma é possível instanciar outras classes, uma para cada banco, como ``PessoasDb()`` que veremos mais pra frente. + +Finalmente, para rodar o programa podemos escrever o código abaixo... + +.. code-block:: python + + if __name__ == '__main__': + cliente = ClientesDb() + cliente.close_connection() + +salvar... e no terminal digitar: + +.. code-block:: bash + + $ python3 connect_db.py + $ ls *.db + +Pronto, o banco *clientes.db* está criado. + +Modo interativo +--------------- + +Legal mesmo é quando usamos o modo interativo para rodar os comandos do python, para isso podemos usar o python3 ou `ipython3 `_. No terminal basta digitar python3 ``ENTER`` que vai aparecer o prompt abaixo (*na mesma pasta do projeto, tá?*) + +.. code-block:: bash + + $ python3 + Python 3.4.0 (default, Apr 11 2014, 13:05:18) + [GCC 4.8.2] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> + +Agora vamos digitar os seguintes comandos, e depois eu explico tudo. + +.. code-block:: python + + >>> from connect_db import Connect + >>> dir(Connect) + >>> db = Connect('clientes.db') + >>> dir(db) + >>> db.close_db() + >>> exit() + +A primeira linha importa a classe ``Connect`` do arquivo *connect_db.py*. + +O comando ``dir(Connect)`` lista todos os métodos da classe ``Connect()``, inclusive ``__init__`` e ``close_db()``. + +``db = Connect('clientes.db')`` cria uma instância da classe ``Connect()`` e usa o argumento ``'clientes.db'`` para criar o banco com o nome especificado. + +o comando ``dir(db)`` lista os métodos da instância. + +E ``db.close_db()`` fecha a conexão com o banco. + + +Criando um banco de dados +------------------------- + +Nosso arquivo principal se chamará `manager_db.py `_ e iremos incrementá-lo aos poucos. Na verdade quando usamos o comando ``c = ClientesDb()`` já criamos o banco de dados com o nome especificado, e instanciamos uma classe chamada ``ClientesDb``. Portanto esta fase já está concluida. + +Mas vou repetir o código inicial para criar e conectar o banco de dados: + +.. code-block:: python + + # manager_db.py + import os + import sqlite3 + import io + import datetime + import names + import csv + from gen_random_values import * + + + class Connect(object): + + def __init__(self, db_name): + try: + # conectando... + self.conn = sqlite3.connect(db_name) + self.cursor = self.conn.cursor() + print("Banco:", db_name) + self.cursor.execute('SELECT SQLITE_VERSION()') + self.data = self.cursor.fetchone() + print("SQLite version: %s" % self.data) + except sqlite3.Error: + print("Erro ao abrir banco.") + return False + + def commit_db(self): + if self.conn: + self.conn.commit() + + def close_db(self): + if self.conn: + self.conn.close() + print("Conexão fechada.") + + + class ClientesDb(object): + + tb_name = 'clientes' + + def __init__(self): + self.db = Connect('clientes.db') + self.tb_name + + def fechar_conexao(self): + self.db.close_db() + + if __name__ == '__main__': + c = ClientesDb() + +Rodando no **terminal**... + +.. code-block:: bash + + $ python3 manager_db.py + $ ls *.db + +O banco ``clientes.db`` está criado. + +Ou no **modo interativo**... + +.. code-block:: python + + $ python3 + >>> from manager_db import * + >>> c = ClientesDb() + Banco: clientes.db + SQLite version: 3.8.2 + >>> exit() + + +Criando uma tabela +------------------ + +Agora é tudo continuação do arquivo `manager_db.py `_ ... + +.. code-block:: python + + def criar_schema(self, schema_name='sql/clientes_schema.sql'): + print("Criando tabela %s ..." % self.tb_name) + + try: + with open(schema_name, 'rt') as f: + schema = f.read() + self.db.cursor.executescript(schema) + except sqlite3.Error: + print("Aviso: A tabela %s já existe." % self.tb_name) + return False + + print("Tabela %s criada com sucesso." % self.tb_name) + + ... + + if __name__ == '__main__': + c = ClientesDb() + c.criar_schema() + +Aqui nós criamos a função ``criar_schema(self, schema_name)`` dentro da classe ``ClientesDb()``. + +Com ``with open(name)`` abrimos o arquivo `clientes_schema.sql `_ . + +Com ``f.read()`` lemos as linhas do arquivo. + +E com `cursor.executescript() `_ executamos a instrução sql que está dentro do arquivo. + +**Modo interativo**... + +.. code-block:: python + + $ python3 + >>> from manager_db import * + >>> c = ClientesDb() + >>> c.criar_schema() + Criando tabela clientes ... + Tabela clientes criada com sucesso. + +Se você digitar no terminal... + +.. code-block:: bash + + $ sqlite3 clientes.db .tables + +Você verá que a tabela foi criada com sucesso. + + + +Create - Inserindo um registro com comando SQL +---------------------------------------------- + +A função a seguir insere um registro na tabela. Repare no uso do comando ``self.db.commit_db()`` que grava de fato os dados. + +.. code-block:: python + + def inserir_um_registro(self): + try: + self.db.cursor.execute(""" + INSERT INTO clientes (nome, idade, cpf, email, fone, cidade, uf, criado_em) + VALUES ('Regis da Silva', 35, '12345678901', 'regis@email.com', '(11) 9876-5342', + 'São Paulo', 'SP', '2014-07-30 11:23:00.199000') + """) + # gravando no bd + self.db.commit_db() + print("Um registro inserido com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + + ... + + if __name__ == '__main__': + c = ClientesDb() + c.criar_schema() + c.inserir_um_registro() + + +Inserindo n registros com uma lista de dados +-------------------------------------------- + +A função a seguir insere vários registros a partir de uma lista. Repare no uso do comando `executemany(sql, [parâmetros]) `_ + +.. code-block:: python + + self.db.cursor.executemany("""INSERT INTO tabela (campos) VALUES (?)""", lista) + +que executa a instrução sql várias vezes. Note também, pela sintaxe, que a quantidade de ``?`` deve ser igual a quantidade de campos, e o parâmetro, no caso está sendo a lista criada. + +.. code-block:: python + + def inserir_com_lista(self): + # criando uma lista de dados + lista = [('Agenor de Sousa', 23, '12345678901', 'agenor@email.com', + '(10) 8300-0000', 'Salvador', 'BA', '2014-07-29 11:23:01.199001'), + ('Bianca Antunes', 21, '12345678902', 'bianca@email.com', + '(10) 8350-0001', 'Fortaleza', 'CE', '2014-07-28 11:23:02.199002'), + ('Carla Ribeiro', 30, '12345678903', 'carla@email.com', + '(10) 8377-0002', 'Campinas', 'SP', '2014-07-28 11:23:03.199003'), + ('Fabiana de Almeida', 25, '12345678904', 'fabiana@email.com', + '(10) 8388-0003', 'São Paulo', 'SP', '2014-07-29 11:23:04.199004'), + ] + try: + self.db.cursor.executemany(""" + INSERT INTO clientes (nome, idade, cpf, email, fone, cidade, uf, criado_em) + VALUES (?,?,?,?,?,?,?,?) + """, lista) + # gravando no bd + self.db.commit_db() + print("Dados inseridos da lista com sucesso: %s registros." % + len(lista)) + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + + + +Inserindo registros de um arquivo externo +----------------------------------------- + +Também podemos escrever as instruções sql num arquivo externo (`clientes_dados.sql `_) e executá-lo com o comando ``executescript(sql_script)``. Note que as instruções a seguir já foram vistas anteriormente. + +.. code-block:: python + + def inserir_de_arquivo(self): + try: + with open('sql/clientes_dados.sql', 'rt') as f: + dados = f.read() + self.db.cursor.executescript(dados) + # gravando no bd + self.db.commit_db() + print("Dados inseridos do arquivo com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + +Importando dados de um arquivo csv +---------------------------------- + +Agora vamos importar os dados de `clientes.csv `_ . A única novidade é o comando `csv.reader() `_ . + +.. code-block:: python + + import csv + ... + + def inserir_de_csv(self, file_name='csv/clientes.csv'): + try: + reader = csv.reader( + open(file_name, 'rt'), delimiter=',') + linha = (reader,) + for linha in reader: + self.db.cursor.execute(""" + INSERT INTO clientes (nome, idade, cpf, email, fone, cidade, uf, criado_em) + VALUES (?,?,?,?,?,?,?,?) + """, linha) + # gravando no bd + self.db.commit_db() + print("Dados importados do csv com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + +**Obs**: Veja em `gen_csv.py `_ como podemos gerar dados randômicos para criar um novo `clientes.csv `_. + +Inserindo um registro com parâmetros de entrada definido pelo usuário +--------------------------------------------------------------------- + +Agora está começando a ficar mais interessante. Quando falamos *parâmetros de entrada* significa interação direta do usuário na aplicação. Ou seja, vamos inserir os dados diretamente pelo terminal em tempo de execução. Para isso nós usamos o comando ``input()`` para Python 3 ou ``raw_input()`` para Python 2. + +.. code-block:: python + + def inserir_com_parametros(self): + # solicitando os dados ao usuário + self.nome = input('Nome: ') + self.idade = input('Idade: ') + self.cpf = input('CPF: ') + self.email = input('Email: ') + self.fone = input('Fone: ') + self.cidade = input('Cidade: ') + self.uf = input('UF: ') or 'SP' + date = datetime.datetime.now().isoformat(" ") + self.criado_em = input('Criado em (%s): ' % date) or date + + try: + self.db.cursor.execute(""" + INSERT INTO clientes (nome, idade, cpf, email, fone, cidade, uf, criado_em) + VALUES (?,?,?,?,?,?,?,?) + """, (self.nome, self.idade, self.cpf, self.email, self.fone, + self.cidade, self.uf, self.criado_em)) + # gravando no bd + self.db.commit_db() + print("Dados inseridos com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + +Note que, em ``criado_em`` se você não informar uma data ele insere a data atual. E os parâmetros informados são passados no final do comando ``execute()``. + +Veja a interação: + +.. code-block:: python + + $ python3 + >>> from manager_db import * + >>> c = ClientesDb() + >>> c.criar_schema() + >>> c.inserir_com_parametros() + Nome: Regis + Idade: 35 + CPF: 11100011100 + Email: regis@email.com + Fone: (11) 1111-1111 + Cidade: São Paulo + UF: SP + Criado em (2014-10-07 01:40:48.836683): + Dados inseridos com sucesso. + + +Inserindo valores randômicos +---------------------------- + +Se lembra de `gen_random_values.py `_? Agora vamos usar ele. + +Para preencher *criado_em* usamos a data atual ``.now()``. + +Para gerar o *nome* usamos a função ``names.get_first_name()`` e ``names.get_last_name()``. + +Para o *email* pegamos a primeira letra do nome e o sobrenome + ``@email.com``, ou seja, o formato r.silva@email.com, por exemplo. + +Para a *cidade* e *uf* usamos a função ``gen_city()`` retornando os dois elementos de ``list_city``. + +O ``repeat`` é 10 por padrão, mas você pode mudar, exemplo ``inserir_randomico(15)`` na chamada da função. + +.. code-block:: python + + def inserir_randomico(self, repeat=10): + ''' Inserir registros com valores randomicos names ''' + lista = [] + for _ in range(repeat): + date = datetime.datetime.now().isoformat(" ") + fname = names.get_first_name() + lname = names.get_last_name() + name = fname + ' ' + lname + email = fname[0].lower() + '.' + lname.lower() + '@email.com' + c = gen_city() + city = c[0] + uf = c[1] + lista.append((name, gen_age(), gen_cpf(), + email, gen_phone(), + city, uf, date)) + try: + self.db.cursor.executemany(""" + INSERT INTO clientes (nome, idade, cpf, email, fone, cidade, uf, criado_em) + VALUES (?,?,?,?,?,?,?,?) + """, lista) + self.db.commit_db() + print("Inserindo %s registros na tabela..." % repeat) + print("Registros criados com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + + + +Read - Lendo os dados +--------------------- + +Eu preferi fazer duas funções ``ler_todos_clientes()`` e ``imprimir_todos_clientes()``. A primeira apenas retorna os valores com o comando ``fetchall()``, pois eu irei usá-lo mais vezes. E a segunda imprime os valores na tela. No caso, eu usei uma tabulação mais bonitinha... + +.. code-block:: python + + def ler_todos_clientes(self): + sql = 'SELECT * FROM clientes ORDER BY nome' + r = self.db.cursor.execute(sql) + return r.fetchall() + + def imprimir_todos_clientes(self): + lista = self.ler_todos_clientes() + print('{:>3s} {:20s} {:<5s} {:15s} {:21s} {:14s} {:15s} {:s} {:s}'.format( + 'id', 'nome', 'idade', 'cpf', 'email', 'fone', 'cidade', 'uf', 'criado_em')) + for c in lista: + print('{:3d} {:23s} {:2d} {:s} {:>25s} {:s} {:15s} {:s} {:s}'.format( + c[0], c[1], c[2], + c[3], c[4], c[5], + c[6], c[7], c[8])) + +mas se quiser você pode usar simplesmente + +.. code-block:: python + + def imprimir_todos_clientes(self): + lista = self.ler_todos_clientes() + for c in lista: + print(c) + +Mais SELECT +----------- + +**Exemplo**: Vamos explorar um pouco mais o ``SELECT``. Veja a seguir como localizar um cliente pelo ``id``. Uma *sutileza* é a vírgula logo depois do ``id``, isto é necessário porque quando usamos a ``?`` é esperado que os parâmetros sejam uma tupla. + +.. code-block:: python + + def localizar_cliente(self, id): + r = self.db.cursor.execute( + 'SELECT * FROM clientes WHERE id = ?', (id,)) + return r.fetchone() + + def imprimir_cliente(self, id): + if self.localizar_cliente(id) == None: + print('Não existe cliente com o id informado.') + else: + print(self.localizar_cliente(id)) + +O ``fetchone()`` retorna apenas uma linha de registro. + + +**Exemplo**: Veja um exemplo de como contar os registros. + +.. code-block:: python + + def contar_cliente(self): + r = self.db.cursor.execute( + 'SELECT COUNT(*) FROM clientes') + print("Total de clientes:", r.fetchone()[0]) + + +**Exemplo**: Contar os clientes maiores que 50 anos de idade. Veja novamente a necessidade da vírgula em ``(t,)``. + +.. code-block:: python + + def contar_cliente_por_idade(self, t=50): + r = self.db.cursor.execute( + 'SELECT COUNT(*) FROM clientes WHERE idade > ?', (t,)) + print("Clientes maiores que", t, "anos:", r.fetchone()[0]) + +Caso queira outra idade mude o valor ao chamar a função: + +.. code-block:: python + + c.contar_cliente_por_idade(18) + + +**Exemplo**: Localizar clientes por idade. + +.. code-block:: python + + def localizar_cliente_por_idade(self, t=50): + resultado = self.db.cursor.execute( + 'SELECT * FROM clientes WHERE idade > ?', (t,)) + print("Clientes maiores que", t, "anos:") + for cliente in resultado.fetchall(): + print(cliente) + + +**Exemplo**: Localizar clientes por uf. + +.. code-block:: python + + def localizar_cliente_por_uf(self, t='SP'): + resultado = self.db.cursor.execute( + 'SELECT * FROM clientes WHERE uf = ?', (t,)) + print("Clientes do estado de", t, ":") + for cliente in resultado.fetchall(): + print(cliente) + + +SELECT personalizado +-------------------- + +**Exemplo**: Vejamos agora como fazer nosso próprio ``SELECT``. + +.. code-block:: python + + def meu_select(self, sql="SELECT * FROM clientes WHERE uf='RJ';"): + r = self.db.cursor.execute(sql) + # gravando no bd + self.db.commit_db() + for cliente in r.fetchall(): + print(cliente) + +Assim, podemos escrever qualquer ``SELECT`` direto na chamada da função: + +.. code-block:: python + + c.meu_select("SELECT * FROM clientes WHERE uf='MG' ORDER BY nome;") + +Acabamos de mudar a função original. Eu coloquei o ``commit_db()`` porque se quiser você pode escrever uma instrução SQL com ``INSERT`` ou ``UPDATE``, por exemplo. + + +**Exemplo**: Lendo instruções de arquivos externos + +No arquivo `clientes_sp.sql `_ eu escrevi várias instruções SQL. + +.. code-block:: sql + + SELECT * FROM clientes WHERE uf='SP'; + SELECT COUNT(*) FROM clientes WHERE uf='SP'; + SELECT * FROM clientes WHERE uf='RJ'; + SELECT COUNT(*) FROM clientes WHERE uf='RJ'; + +Para que todas as instruções sejam lidas e retorne valores é necessário que usemos os comandos ``split(';')`` para informar ao interpretador qual é o final de cada linha. E o comando ``execute()`` dentro de um ``for``, assim ele lê e executa todas as instruções SQL do arquivo. + +.. code-block:: python + + def ler_arquivo(self, file_name='sql/clientes_sp.sql'): + with open(file_name, 'rt') as f: + dados = f.read() + sqlcomandos = dados.split(';') + print("Consulta feita a partir de arquivo externo.") + for comando in sqlcomandos: + r = self.db.cursor.execute(comando) + for c in r.fetchall(): + print(c) + # gravando no bd + self.db.commit_db() + +Novamente você pode usar qualquer instrução SQL porque o ``commit_db()`` já está ai. + +.. code-block:: python + + c.ler_arquivo('sql/clientes_maior60.sql') + + +Update - Alterando os dados +--------------------------- + +Nenhuma novidade, todos os comandos já foram vistos antes. No caso, informamos o ``id`` do cliente. Veja que aqui usamos novamente a função ``localizar_cliente(id)`` para localizar o cliente. + +.. code-block:: python + + def atualizar(self, id): + try: + c = self.localizar_cliente(id) + if c: + # solicitando os dados ao usuário + # se for no python2.x digite entre aspas simples + self.novo_fone = input('Fone: ') + self.db.cursor.execute(""" + UPDATE clientes + SET fone = ? + WHERE id = ? + """, (self.novo_fone, id,)) + # gravando no bd + self.db.commit_db() + print("Dados atualizados com sucesso.") + else: + print('Não existe cliente com o id informado.') + except e: + raise e + +Chamando a função: + +.. code-block:: python + + c.atualizar(10) + + +Delete - Deletando os dados +--------------------------- + +Novamente vamos localizar o cliente para depois deletá-lo. + +.. code-block:: python + + def deletar(self, id): + try: + c = self.localizar_cliente(id) + # verificando se existe cliente com o ID passado, caso exista + if c: + self.db.cursor.execute(""" + DELETE FROM clientes WHERE id = ? + """, (id,)) + # gravando no bd + self.db.commit_db() + print("Registro %d excluído com sucesso." % id) + else: + print('Não existe cliente com o código informado.') + except e: + raise e + +Chamando a função: + +.. code-block:: python + + c.deletar(10) + + +Adicionando uma nova coluna +--------------------------- + +Para adicionar uma nova coluna é bem simples. + +.. code-block:: python + + def alterar_tabela(self): + try: + self.db.cursor.execute(""" + ALTER TABLE clientes + ADD COLUMN bloqueado BOOLEAN; + """) + # gravando no bd + self.db.commit_db() + print("Novo campo adicionado com sucesso.") + except sqlite3.OperationalError: + print("Aviso: O campo 'bloqueado' já existe.") + return False + + + +Lendo as informações do banco de dados +-------------------------------------- + +Obtendo informações da tabela + +.. code-block:: python + + def table_info(self): + t = self.db.cursor.execute( + 'PRAGMA table_info({})'.format(self.tb_name)) + colunas = [tupla[1] for tupla in t.fetchall()] + print('Colunas:', colunas) + +Chamando e vendo o resultado: + +.. code-block:: bash + + >>> c.table_info() + Colunas: ['id', 'nome', 'idade', 'cpf', 'email', 'fone', 'cidade', 'uf', 'criado_em'] + + +Listando as tabelas do bd + +.. code-block:: python + + def table_list(self): + l = self.db.cursor.execute(""" + SELECT name FROM sqlite_master WHERE type='table' ORDER BY name + """) + print('Tabelas:') + for tabela in l.fetchall(): + print("%s" % (tabela)) + +Chamando e vendo o resultado: + +.. code-block:: bash + + >>> c.table_list() + Tabelas: + clientes + sqlite_sequence + + +Obtendo o schema da tabela + +.. code-block:: python + + def table_schema(self): + s = self.db.cursor.execute(""" + SELECT sql FROM sqlite_master WHERE type='table' AND name=? + """, (self.tb_name,)) + + print('Schema:') + for schema in s.fetchall(): + print("%s" % (schema)) + +Chamando e vendo o resultado: + +.. code-block:: sql + + >>> c.table_schema() + Schema: + CREATE TABLE clientes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nome TEXT NOT NULL, + idade INTEGER, + cpf VARCHAR(11) NOT NULL, + email TEXT NOT NULL UNIQUE, + fone TEXT, + cidade TEXT, + uf VARCHAR(2) NOT NULL, + criado_em DATETIME NOT NULL + ) + + +Fazendo backup do banco de dados (exportando dados) +--------------------------------------------------- + +.. code-block:: python + + import io + ... + def backup(self, file_name='sql/clientes_bkp.sql'): + with io.open(file_name, 'w') as f: + for linha in self.db.conn.iterdump(): + f.write('%s\n' % linha) + + print('Backup realizado com sucesso.') + print('Salvo como %s' % file_name) + +Se quiser pode salvar com outro nome. + +.. code-block:: python + + c.backup('sql/clientes_backup.sql') + + +Recuperando o banco de dados (importando dados) +----------------------------------------------- + +Aqui nós usamos dois parâmetros: ``db_name`` para o banco de dados recuperado (no caso, um banco novo) e ``file_name`` para o nome do arquivo de backup com as instruções SQL salvas. + +.. code-block:: python + + def importar_dados(self, db_name='clientes_recovery.db', file_name='sql/clientes_bkp.sql'): + try: + self.db = Connect(db_name) + f = io.open(file_name, 'r') + sql = f.read() + self.db.cursor.executescript(sql) + print('Banco de dados recuperado com sucesso.') + print('Salvo como %s' % db_name) + except sqlite3.OperationalError: + print( + "Aviso: O banco de dados %s já existe. Exclua-o e faça novamente." % + db_name) + return False + +Fechando conexão: + +.. code-block:: python + + def fechar_conexao(self): + self.db.close_db() + + +Conectando-se a outro banco +--------------------------- + +Agora, no mesmo arquivo `manager_db.py `_ vamos criar uma outra instância chamada ``PessoasDb()``. Neste exemplo vamos relacionar duas tabelas: ``pessoas`` e ``cidades``. + +Veja na figura a seguir como as tabelas se relacionam. + +.. image:: images/regisdasilva/tabelas.png + :alt: tabelas.png + +Agora os códigos: + +.. code-block:: python + + class PessoasDb(object): + + tb_name = 'pessoas' + + def __init__(self): + self.db = Connect('pessoas.db') + self.tb_name + +Criando o *schema* a partir de `pessoas_schema.sql `_. + +.. code-block:: python + + def criar_schema(self, schema_name='sql/pessoas_schema.sql'): + print("Criando tabela %s ..." % self.tb_name) + + try: + with open(schema_name, 'rt') as f: + schema = f.read() + self.db.cursor.executescript(schema) + except sqlite3.Error: + print("Aviso: A tabela %s já existe." % self.tb_name) + return False + + print("Tabela %s criada com sucesso." % self.tb_name) + +Inserindo as cidades a partir de `cidades.csv `_. + +.. code-block:: python + + def inserir_de_csv(self, file_name='csv/cidades.csv'): + try: + c = csv.reader( + open(file_name, 'rt'), delimiter=',') + t = (c,) + for t in c: + self.db.cursor.execute(""" + INSERT INTO cidades (cidade, uf) + VALUES (?,?) + """, t) + # gravando no bd + self.db.commit_db() + print("Dados importados do csv com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: A cidade deve ser única.") + return False + +Agora vamos contar quantas cidades temos na tabela... + +.. code-block:: python + + def gen_cidade(self): + ''' conta quantas cidades estão cadastradas e escolhe uma delas pelo id. ''' + sql = 'SELECT COUNT(*) FROM cidades' + q = self.db.cursor.execute(sql) + return q.fetchone()[0] + +para a partir dai gerar valores randômicos apenas com as cidades existentes. + +.. code-block:: python + + def inserir_randomico(self, repeat=10): + lista = [] + for _ in range(repeat): + fname = names.get_first_name() + lname = names.get_last_name() + email = fname[0].lower() + '.' + lname.lower() + '@email.com' + cidade_id = random.randint(1, self.gen_cidade()) + lista.append((fname, lname, email, cidade_id)) + try: + self.db.cursor.executemany(""" + INSERT INTO pessoas (nome, sobrenome, email, cidade_id) + VALUES (?,?,?,?) + """, lista) + self.db.commit_db() + print("Inserindo %s registros na tabela..." % repeat) + print("Registros criados com sucesso.") + except sqlite3.IntegrityError: + print("Aviso: O email deve ser único.") + return False + +Agora é só alegria! + +.. code-block:: python + + def ler_todas_pessoas(self): + sql = 'SELECT * FROM pessoas INNER JOIN cidades ON pessoas.cidade_id = cidades.id' + r = self.db.cursor.execute(sql) + return r.fetchall() + + def imprimir_todas_pessoas(self): + lista = self.ler_todas_pessoas() + for c in lista: + print(c) + + # myselect, imprime todos os nomes que começam com R + def meu_select(self, sql="SELECT * FROM pessoas WHERE nome LIKE 'R%' ORDER BY nome;"): + r = self.db.cursor.execute(sql) + self.db.commit_db() + print('Nomes que começam com R:') + for c in r.fetchall(): + print(c) + + def table_list(self): + # listando as tabelas do bd + l = self.db.cursor.execute(""" + SELECT name FROM sqlite_master WHERE type='table' ORDER BY name + """) + print('Tabelas:') + for tabela in l.fetchall(): + print("%s" % (tabela)) + + def fechar_conexao(self): + self.db.close_db() + +Chamando tudo no **modo interativo** + +.. code-block:: python + + >>> from manager_db import * + >>> p = PessoasDb() + >>> p.criar_schema() + >>> p.inserir_de_csv() + >>> p.gen_cidade() + >>> p.inserir_randomico(100) + >>> p.imprimir_todas_pessoas() + >>> p.meu_select() + >>> p.table_list() + >>> p.fechar_conexao() + + +Exemplos +-------- + +Assista os videos no `youtube `_. + +Veja os exemplos no `github `_. + +Referências +----------- + +`sqlite3 — DB-API 2.0 interface for SQLite databases `_ +`sqlite3 Embedded Relational Database `_ +`Lets Talk to a SQLite Database with Python `_ +`Advanced SQLite Usage in Python `_ +`Python A Simple Step by Step SQLite Tutorial `_ \ No newline at end of file diff --git a/content/github-pages-com-pelican-e-travis-ci.rst b/content/github-pages-com-pelican-e-travis-ci.rst new file mode 100644 index 000000000..22fe6c91a --- /dev/null +++ b/content/github-pages-com-pelican-e-travis-ci.rst @@ -0,0 +1,261 @@ +GitHub Pages com Pelican e Travis-CI +==================================== + +:slug: github-pages-com-pelican-e-travis-ci +:date: 2016-05-04 21:46 +:tags: tutorial,gh-pages,pelican,travis-ci +:category: Pelican +:author: Humberto Rocha +:email: humrochagf@gmail.com +:github: humrochagf +:twitter: humrochagf +:facebook: humrochagf +:linkedin: humrochagf + +**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: + +.. figure:: /images/humrochagf/gh-pelican-travis/pagina-usuario.png + :alt: Repositório da página de usuário + :align: center + +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``: + +.. figure:: /images/humrochagf/gh-pelican-travis/pagina-projeto.png + :alt: Repositório da página de projeto + :align: center + +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: + +.. code-block:: bash + + $ pip install pelican==3.6 + +Para criar um projeto faça: + +.. code-block:: bash + + $ 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**: + +.. code-block:: bash + + $ 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: + +.. code-block:: bash + + $ 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: + +.. code-block:: bash + + $ make github + +.. figure:: /images/humrochagf/gh-pelican-travis/blog.png + :alt: Primeira publicação do blog + :align: center + +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. + +.. figure:: /images/humrochagf/gh-pelican-travis/travis-repo1.png + :alt: Habilitando repositório no travis + :align: center + +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. + +.. figure:: /images/humrochagf/gh-pelican-travis/travis-repo2.png + :alt: Configurando remositório no travis + :align: center + +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: + +.. code-block:: bash + + $ 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: + +.. code-block:: bash + + $ 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: + +.. code-block:: bash + + $ touch .travis.yml + +E adicionar o seguinte conteúdo: + +.. code-block:: yaml + + 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: + +.. code-block:: bash + + $ 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: + +.. code-block:: bash + + $ 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**: + +.. figure:: /images/humrochagf/gh-pelican-travis/deploy-key.png + :alt: Adicionando a deploy key no github + :align: center + +Pronto, agora podemos publicar conteúdo em nosso blog sem a necessidade de ter o pelican instalado na máquina: + +.. figure:: /images/humrochagf/gh-pelican-travis/primeira-postagem1.png + :alt: Fazendo a primeira postagem + :align: center + +Que o travis irá publicar para você: + +.. figure:: /images/humrochagf/gh-pelican-travis/primeira-postagem2.png + :alt: Blog com a primeira postagem + :align: center + +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. + +.. _df.python.org.br/blog/github-pages-com-pelican-e-travis-ci: http://df.python.org.br/blog/github-pages-com-pelican-e-travis-ci +.. _GitHub Pages: http://pages.github.com +.. _Pelican: http://blog.getpelican.com +.. _Travis-CI: https://travis-ci.org +.. _GitHub: http://github.com +.. _Travis-CLI: https://github.com/travis-ci/travis.rb +.. _Mind Bending: http://mindbending.org/pt/series/migrando-para-o-pelican diff --git a/content/images/andrealmar/Generator_FooFighters.jpg b/content/images/andrealmar/Generator_FooFighters.jpg new file mode 100644 index 000000000..139ecea50 Binary files /dev/null and b/content/images/andrealmar/Generator_FooFighters.jpg differ diff --git a/content/images/andrelramos/ocr2.png b/content/images/andrelramos/ocr2.png new file mode 100644 index 000000000..112b855fe Binary files /dev/null and b/content/images/andrelramos/ocr2.png differ diff --git a/content/images/arthur-alves/captaoboing.png b/content/images/arthur-alves/captaoboing.png index d3abb7f72..35ccff5d4 100644 Binary files a/content/images/arthur-alves/captaoboing.png and b/content/images/arthur-alves/captaoboing.png differ diff --git a/content/images/drgarcia1986/debugging.png b/content/images/drgarcia1986/debugging.png new file mode 100644 index 000000000..b7dde7909 Binary files /dev/null and b/content/images/drgarcia1986/debugging.png differ diff --git a/content/images/drgarcia1986/heroku.png b/content/images/drgarcia1986/heroku.png new file mode 100644 index 000000000..a0eec946d Binary files /dev/null and b/content/images/drgarcia1986/heroku.png differ diff --git a/content/images/drgarcia1986/is_a_trap.png b/content/images/drgarcia1986/is_a_trap.png new file mode 100644 index 000000000..1540dd275 Binary files /dev/null and b/content/images/drgarcia1986/is_a_trap.png differ diff --git a/content/images/drgarcia1986/locust.png b/content/images/drgarcia1986/locust.png new file mode 100644 index 000000000..2fe6c626c Binary files /dev/null and b/content/images/drgarcia1986/locust.png differ diff --git a/content/images/drgarcia1986/locust_distributed.png b/content/images/drgarcia1986/locust_distributed.png new file mode 100644 index 000000000..30ef5f5c1 Binary files /dev/null and b/content/images/drgarcia1986/locust_distributed.png differ diff --git a/content/images/drgarcia1986/locust_inicial.png b/content/images/drgarcia1986/locust_inicial.png new file mode 100644 index 000000000..2dd3da3fe Binary files /dev/null and b/content/images/drgarcia1986/locust_inicial.png differ diff --git a/content/images/drgarcia1986/locust_name.png b/content/images/drgarcia1986/locust_name.png new file mode 100644 index 000000000..8eaac1451 Binary files /dev/null and b/content/images/drgarcia1986/locust_name.png differ diff --git a/content/images/drgarcia1986/locust_random.png b/content/images/drgarcia1986/locust_random.png new file mode 100644 index 000000000..5abcc1fd1 Binary files /dev/null and b/content/images/drgarcia1986/locust_random.png differ diff --git a/content/images/drgarcia1986/locust_session_fail.png b/content/images/drgarcia1986/locust_session_fail.png new file mode 100644 index 000000000..fdae2e289 Binary files /dev/null and b/content/images/drgarcia1986/locust_session_fail.png differ diff --git a/content/images/drgarcia1986/locust_session_success.png b/content/images/drgarcia1986/locust_session_success.png new file mode 100644 index 000000000..cfcfba347 Binary files /dev/null and b/content/images/drgarcia1986/locust_session_success.png differ diff --git a/content/images/drgarcia1986/the_locust.jpg b/content/images/drgarcia1986/the_locust.jpg new file mode 100644 index 000000000..71778eaa8 Binary files /dev/null and b/content/images/drgarcia1986/the_locust.jpg differ diff --git a/content/images/dyesten/cadastro_cloudinary.png b/content/images/dyesten/cadastro_cloudinary.png new file mode 100644 index 000000000..c7d39a795 Binary files /dev/null and b/content/images/dyesten/cadastro_cloudinary.png differ diff --git a/content/images/dyesten/template_inicial.png b/content/images/dyesten/template_inicial.png new file mode 100644 index 000000000..6d4a6369d Binary files /dev/null and b/content/images/dyesten/template_inicial.png differ diff --git a/content/images/eduardo-matos/criando-permissao.png b/content/images/eduardo-matos/criando-permissao.png new file mode 100644 index 000000000..fb5bd2124 Binary files /dev/null and b/content/images/eduardo-matos/criando-permissao.png differ diff --git a/content/images/eduardo-matos/permissao-de-usuario.png b/content/images/eduardo-matos/permissao-de-usuario.png new file mode 100644 index 000000000..5a8279aa3 Binary files /dev/null and b/content/images/eduardo-matos/permissao-de-usuario.png differ diff --git a/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png b/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png new file mode 100644 index 000000000..9a126c665 Binary files /dev/null and b/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png differ diff --git a/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png new file mode 100644 index 000000000..b999023c7 Binary files /dev/null and b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png differ diff --git a/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png new file mode 100644 index 000000000..4fa54d20f Binary files /dev/null and b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png differ diff --git a/content/images/ehriq/ConfigPyCharm.png b/content/images/ehriq/ConfigPyCharm.png new file mode 100644 index 000000000..601b78636 Binary files /dev/null and b/content/images/ehriq/ConfigPyCharm.png differ diff --git a/content/images/erichideki/bottle.png b/content/images/erichideki/bottle.png new file mode 100644 index 000000000..1f3edb30b Binary files /dev/null and b/content/images/erichideki/bottle.png differ diff --git a/content/images/erichideki/dreampie.jpg b/content/images/erichideki/dreampie.jpg new file mode 100644 index 000000000..9073bd5b5 Binary files /dev/null and b/content/images/erichideki/dreampie.jpg differ diff --git a/content/images/hudsonbrendon/django-fig.png b/content/images/hudsonbrendon/django-fig.png new file mode 100644 index 000000000..6e594a7c6 Binary files /dev/null and b/content/images/hudsonbrendon/django-fig.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/banner.png b/content/images/humrochagf/gh-pelican-travis/banner.png new file mode 100644 index 000000000..559f0bbbf Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/banner.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/blog.png b/content/images/humrochagf/gh-pelican-travis/blog.png new file mode 100644 index 000000000..1935a7aac Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/blog.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/deploy-key.png b/content/images/humrochagf/gh-pelican-travis/deploy-key.png new file mode 100644 index 000000000..55a55f222 Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/deploy-key.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png b/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png new file mode 100644 index 000000000..d7ea9061d Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png b/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png new file mode 100644 index 000000000..0dfd1dc13 Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png b/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png new file mode 100644 index 000000000..37621788f Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png b/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png new file mode 100644 index 000000000..01097f831 Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/travis-repo1.png b/content/images/humrochagf/gh-pelican-travis/travis-repo1.png new file mode 100644 index 000000000..86cb10d92 Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/travis-repo1.png differ diff --git a/content/images/humrochagf/gh-pelican-travis/travis-repo2.png b/content/images/humrochagf/gh-pelican-travis/travis-repo2.png new file mode 100644 index 000000000..aedb5a429 Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/travis-repo2.png differ diff --git a/content/images/humrochagf/meusite-admin.png b/content/images/humrochagf/meusite-admin.png new file mode 100644 index 000000000..6c16b22b5 Binary files /dev/null and b/content/images/humrochagf/meusite-admin.png differ diff --git a/content/images/humrochagf/meusite.png b/content/images/humrochagf/meusite.png new file mode 100644 index 000000000..98cfc3eb4 Binary files /dev/null and b/content/images/humrochagf/meusite.png differ diff --git a/content/images/juniorcarvalho/dokku-setup.jpg b/content/images/juniorcarvalho/dokku-setup.jpg new file mode 100644 index 000000000..bfa4ad726 Binary files /dev/null and b/content/images/juniorcarvalho/dokku-setup.jpg differ diff --git a/content/images/lucasmagnum/helloworld.png b/content/images/lucasmagnum/helloworld.png new file mode 100644 index 000000000..363e2eecd Binary files /dev/null and b/content/images/lucasmagnum/helloworld.png differ diff --git a/content/images/lucasmagnum/itworked.png b/content/images/lucasmagnum/itworked.png new file mode 100644 index 000000000..4692f857d Binary files /dev/null and b/content/images/lucasmagnum/itworked.png differ diff --git a/content/images/mstuttgart/snapshot_17.png b/content/images/mstuttgart/snapshot_17.png new file mode 100644 index 000000000..34199b251 Binary files /dev/null and b/content/images/mstuttgart/snapshot_17.png differ diff --git a/content/images/mstuttgart/snapshot_18.png b/content/images/mstuttgart/snapshot_18.png new file mode 100644 index 000000000..eaaf3e097 Binary files /dev/null and b/content/images/mstuttgart/snapshot_18.png differ diff --git a/content/images/mstuttgart/snapshot_19.png b/content/images/mstuttgart/snapshot_19.png new file mode 100644 index 000000000..6e0a3e827 Binary files /dev/null and b/content/images/mstuttgart/snapshot_19.png differ diff --git a/content/images/mstuttgart/snapshot_20.png b/content/images/mstuttgart/snapshot_20.png new file mode 100644 index 000000000..4df153ce0 Binary files /dev/null and b/content/images/mstuttgart/snapshot_20.png differ diff --git a/content/images/mstuttgart/snapshot_21.png b/content/images/mstuttgart/snapshot_21.png new file mode 100644 index 000000000..fc87e91e8 Binary files /dev/null and b/content/images/mstuttgart/snapshot_21.png differ diff --git a/content/images/mstuttgart/snapshot_22.png b/content/images/mstuttgart/snapshot_22.png new file mode 100644 index 000000000..dae0f5472 Binary files /dev/null and b/content/images/mstuttgart/snapshot_22.png differ diff --git a/content/images/mstuttgart/snapshot_23.png b/content/images/mstuttgart/snapshot_23.png new file mode 100644 index 000000000..8c1820168 Binary files /dev/null and b/content/images/mstuttgart/snapshot_23.png differ diff --git a/content/images/mstuttgart/snapshot_24.png b/content/images/mstuttgart/snapshot_24.png new file mode 100644 index 000000000..4de56ec0c Binary files /dev/null and b/content/images/mstuttgart/snapshot_24.png differ diff --git a/content/images/mstuttgart/snapshot_25.png b/content/images/mstuttgart/snapshot_25.png new file mode 100644 index 000000000..b8c1257ea Binary files /dev/null and b/content/images/mstuttgart/snapshot_25.png differ diff --git a/content/images/mstuttgart/snapshot_26.png b/content/images/mstuttgart/snapshot_26.png new file mode 100644 index 000000000..79aa775f8 Binary files /dev/null and b/content/images/mstuttgart/snapshot_26.png differ diff --git a/content/images/mstuttgart/snapshot_27.png b/content/images/mstuttgart/snapshot_27.png new file mode 100644 index 000000000..0fd89c3c3 Binary files /dev/null and b/content/images/mstuttgart/snapshot_27.png differ diff --git a/content/images/mstuttgart/snapshot_28.png b/content/images/mstuttgart/snapshot_28.png new file mode 100644 index 000000000..6273a117e Binary files /dev/null and b/content/images/mstuttgart/snapshot_28.png differ diff --git a/content/images/mstuttgart/snapshot_29.png b/content/images/mstuttgart/snapshot_29.png new file mode 100644 index 000000000..6273a117e Binary files /dev/null and b/content/images/mstuttgart/snapshot_29.png differ diff --git a/content/images/mstuttgart/snapshot_30.png b/content/images/mstuttgart/snapshot_30.png new file mode 100644 index 000000000..4cc306018 Binary files /dev/null and b/content/images/mstuttgart/snapshot_30.png differ diff --git a/content/images/mstuttgart/snapshot_31.png b/content/images/mstuttgart/snapshot_31.png new file mode 100644 index 000000000..0546a5b27 Binary files /dev/null and b/content/images/mstuttgart/snapshot_31.png differ diff --git a/content/images/mstuttgart/snapshot_32.png b/content/images/mstuttgart/snapshot_32.png new file mode 100644 index 000000000..2676a2360 Binary files /dev/null and b/content/images/mstuttgart/snapshot_32.png differ diff --git a/content/images/mstuttgart/snapshot_33.png b/content/images/mstuttgart/snapshot_33.png new file mode 100644 index 000000000..5aef9e56e Binary files /dev/null and b/content/images/mstuttgart/snapshot_33.png differ diff --git a/content/images/mstuttgart/snapshot_34.png b/content/images/mstuttgart/snapshot_34.png new file mode 100644 index 000000000..c21cfea8e Binary files /dev/null and b/content/images/mstuttgart/snapshot_34.png differ diff --git a/content/images/mstuttgart/snapshot_35.png b/content/images/mstuttgart/snapshot_35.png new file mode 100644 index 000000000..eae50680f Binary files /dev/null and b/content/images/mstuttgart/snapshot_35.png differ diff --git a/content/images/mstuttgart/snapshot_36.png b/content/images/mstuttgart/snapshot_36.png new file mode 100644 index 000000000..fa6d59b81 Binary files /dev/null and b/content/images/mstuttgart/snapshot_36.png differ diff --git a/content/images/mstuttgart/snapshot_37.png b/content/images/mstuttgart/snapshot_37.png new file mode 100644 index 000000000..539859f30 Binary files /dev/null and b/content/images/mstuttgart/snapshot_37.png differ diff --git a/content/images/mstuttgart/snapshot_38.png b/content/images/mstuttgart/snapshot_38.png new file mode 100644 index 000000000..b5f6372b9 Binary files /dev/null and b/content/images/mstuttgart/snapshot_38.png differ diff --git a/content/images/mstuttgart/snapshot_39.png b/content/images/mstuttgart/snapshot_39.png new file mode 100644 index 000000000..4d7488eaa Binary files /dev/null and b/content/images/mstuttgart/snapshot_39.png differ diff --git a/content/images/mstuttgart/snapshot_40.png b/content/images/mstuttgart/snapshot_40.png new file mode 100644 index 000000000..6279fed38 Binary files /dev/null and b/content/images/mstuttgart/snapshot_40.png differ diff --git a/content/images/mstuttgart/snapshot_41.png b/content/images/mstuttgart/snapshot_41.png new file mode 100644 index 000000000..a32a23568 Binary files /dev/null and b/content/images/mstuttgart/snapshot_41.png differ diff --git a/content/images/mstuttgart/snapshot_42.png b/content/images/mstuttgart/snapshot_42.png new file mode 100644 index 000000000..02d9789f4 Binary files /dev/null and b/content/images/mstuttgart/snapshot_42.png differ diff --git a/content/images/mstuttgart/snapshot_43.png b/content/images/mstuttgart/snapshot_43.png new file mode 100644 index 000000000..e70cf29ad Binary files /dev/null and b/content/images/mstuttgart/snapshot_43.png differ diff --git a/content/images/mstuttgart/snapshot_44.png b/content/images/mstuttgart/snapshot_44.png new file mode 100644 index 000000000..e9df7ac60 Binary files /dev/null and b/content/images/mstuttgart/snapshot_44.png differ diff --git a/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png b/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png new file mode 100644 index 000000000..ea3f26071 Binary files /dev/null and b/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png differ diff --git a/content/images/rafpyprog/pybr-banner.jpeg b/content/images/rafpyprog/pybr-banner.jpeg new file mode 100644 index 000000000..1008c6d7c Binary files /dev/null and b/content/images/rafpyprog/pybr-banner.jpeg differ diff --git a/content/images/ramalho/Tweedledum-Tweedledee_500x390.png b/content/images/ramalho/Tweedledum-Tweedledee_500x390.png new file mode 100644 index 000000000..6f7194aff Binary files /dev/null and b/content/images/ramalho/Tweedledum-Tweedledee_500x390.png differ diff --git a/content/images/ramalho/diagrams/dum-dee.png b/content/images/ramalho/diagrams/dum-dee.png new file mode 100644 index 000000000..6e0220213 Binary files /dev/null and b/content/images/ramalho/diagrams/dum-dee.png differ diff --git a/content/images/ramalho/diagrams/dum-skills-references.png b/content/images/ramalho/diagrams/dum-skills-references.png new file mode 100644 index 000000000..af09581b2 Binary files /dev/null and b/content/images/ramalho/diagrams/dum-skills-references.png differ diff --git a/content/images/ramalho/diagrams/dum-t_doom-dee.png b/content/images/ramalho/diagrams/dum-t_doom-dee.png new file mode 100644 index 000000000..812501001 Binary files /dev/null and b/content/images/ramalho/diagrams/dum-t_doom-dee.png differ diff --git a/content/images/regisdasilva/admin.png b/content/images/regisdasilva/admin.png new file mode 100644 index 000000000..454ff65a7 Binary files /dev/null and b/content/images/regisdasilva/admin.png differ diff --git a/content/images/regisdasilva/band_detail.png b/content/images/regisdasilva/band_detail.png new file mode 100644 index 000000000..bcdbbcf16 Binary files /dev/null and b/content/images/regisdasilva/band_detail.png differ diff --git a/content/images/regisdasilva/band_form.png b/content/images/regisdasilva/band_form.png new file mode 100644 index 000000000..25d4d7694 Binary files /dev/null and b/content/images/regisdasilva/band_form.png differ diff --git a/content/images/regisdasilva/band_listing.png b/content/images/regisdasilva/band_listing.png new file mode 100644 index 000000000..fd98466be Binary files /dev/null and b/content/images/regisdasilva/band_listing.png differ diff --git a/content/images/regisdasilva/drf01.png b/content/images/regisdasilva/drf01.png new file mode 100644 index 000000000..120526207 Binary files /dev/null and b/content/images/regisdasilva/drf01.png differ diff --git a/content/images/regisdasilva/drf02.png b/content/images/regisdasilva/drf02.png new file mode 100644 index 000000000..07958dbb8 Binary files /dev/null and b/content/images/regisdasilva/drf02.png differ diff --git a/content/images/regisdasilva/erd.png b/content/images/regisdasilva/erd.png new file mode 100644 index 000000000..3c8e81dc0 Binary files /dev/null and b/content/images/regisdasilva/erd.png differ diff --git a/content/images/regisdasilva/erd_vendas.png b/content/images/regisdasilva/erd_vendas.png new file mode 100644 index 000000000..6b087ffb5 Binary files /dev/null and b/content/images/regisdasilva/erd_vendas.png differ diff --git a/content/images/regisdasilva/form.png b/content/images/regisdasilva/form.png new file mode 100644 index 000000000..c7c2a57af Binary files /dev/null and b/content/images/regisdasilva/form.png differ diff --git a/content/images/regisdasilva/home.png b/content/images/regisdasilva/home.png new file mode 100644 index 000000000..18a0e6ecc Binary files /dev/null and b/content/images/regisdasilva/home.png differ diff --git a/content/images/regisdasilva/member_form.png b/content/images/regisdasilva/member_form.png new file mode 100644 index 000000000..40d8aa02c Binary files /dev/null and b/content/images/regisdasilva/member_form.png differ diff --git a/content/images/regisdasilva/person.jpg b/content/images/regisdasilva/person.jpg new file mode 100644 index 000000000..12617f010 Binary files /dev/null and b/content/images/regisdasilva/person.jpg differ diff --git a/content/images/regisdasilva/postgresql.png b/content/images/regisdasilva/postgresql.png new file mode 100644 index 000000000..c25aab2bd Binary files /dev/null and b/content/images/regisdasilva/postgresql.png differ diff --git a/content/images/regisdasilva/postgresql_django.png b/content/images/regisdasilva/postgresql_django.png new file mode 100644 index 000000000..4000aac68 Binary files /dev/null and b/content/images/regisdasilva/postgresql_django.png differ diff --git a/content/images/regisdasilva/postgresql_python.png b/content/images/regisdasilva/postgresql_python.png new file mode 100644 index 000000000..fb738665a Binary files /dev/null and b/content/images/regisdasilva/postgresql_python.png differ diff --git a/content/images/regisdasilva/tabelas.png b/content/images/regisdasilva/tabelas.png new file mode 100644 index 000000000..0006e97d6 Binary files /dev/null and b/content/images/regisdasilva/tabelas.png differ diff --git a/content/images/regisdasilva/youtube_logo.png b/content/images/regisdasilva/youtube_logo.png new file mode 100644 index 000000000..f8c2f9532 Binary files /dev/null and b/content/images/regisdasilva/youtube_logo.png differ diff --git a/content/images/rochacbruno/access.png b/content/images/rochacbruno/access.png new file mode 100644 index 000000000..a7c86f5c6 Binary files /dev/null and b/content/images/rochacbruno/access.png differ diff --git a/content/images/rochacbruno/admin.jpg b/content/images/rochacbruno/admin.jpg new file mode 100644 index 000000000..b508f0b11 Binary files /dev/null and b/content/images/rochacbruno/admin.jpg differ diff --git a/content/images/rochacbruno/admin_index.png b/content/images/rochacbruno/admin_index.png new file mode 100644 index 000000000..6785e1b67 Binary files /dev/null and b/content/images/rochacbruno/admin_index.png differ diff --git a/content/images/rochacbruno/admin_index_login.png b/content/images/rochacbruno/admin_index_login.png new file mode 100644 index 000000000..99ddd0b45 Binary files /dev/null and b/content/images/rochacbruno/admin_index_login.png differ diff --git a/content/images/rochacbruno/admin_noticia.png b/content/images/rochacbruno/admin_noticia.png new file mode 100644 index 000000000..7d69c6614 Binary files /dev/null and b/content/images/rochacbruno/admin_noticia.png differ diff --git a/content/images/rochacbruno/admin_user_columns.png b/content/images/rochacbruno/admin_user_columns.png new file mode 100644 index 000000000..c33cc1ec9 Binary files /dev/null and b/content/images/rochacbruno/admin_user_columns.png differ diff --git a/content/images/rochacbruno/admin_user_full.png b/content/images/rochacbruno/admin_user_full.png new file mode 100644 index 000000000..eeb5b91be Binary files /dev/null and b/content/images/rochacbruno/admin_user_full.png differ diff --git a/content/images/rochacbruno/bootstrap.png b/content/images/rochacbruno/bootstrap.png new file mode 100644 index 000000000..34aceded9 Binary files /dev/null and b/content/images/rochacbruno/bootstrap.png differ diff --git a/content/images/rochacbruno/cached.png b/content/images/rochacbruno/cached.png new file mode 100644 index 000000000..226fddd63 Binary files /dev/null and b/content/images/rochacbruno/cached.png differ diff --git a/content/images/rochacbruno/cms_index.png b/content/images/rochacbruno/cms_index.png new file mode 100644 index 000000000..73567ac48 Binary files /dev/null and b/content/images/rochacbruno/cms_index.png differ diff --git a/content/images/rochacbruno/code.png b/content/images/rochacbruno/code.png new file mode 100644 index 000000000..1fe7d8532 Binary files /dev/null and b/content/images/rochacbruno/code.png differ diff --git a/content/images/rochacbruno/deboas.jpg b/content/images/rochacbruno/deboas.jpg new file mode 100644 index 000000000..ce7719f0a Binary files /dev/null and b/content/images/rochacbruno/deboas.jpg differ diff --git a/content/images/rochacbruno/debug_toolbar.png b/content/images/rochacbruno/debug_toolbar.png new file mode 100644 index 000000000..d00eb3a25 Binary files /dev/null and b/content/images/rochacbruno/debug_toolbar.png differ diff --git a/content/images/rochacbruno/lego_snake.jpg b/content/images/rochacbruno/lego_snake.jpg new file mode 100644 index 000000000..5916aa579 Binary files /dev/null and b/content/images/rochacbruno/lego_snake.jpg differ diff --git a/content/images/rochacbruno/login.png b/content/images/rochacbruno/login.png new file mode 100644 index 000000000..1a6de79f4 Binary files /dev/null and b/content/images/rochacbruno/login.png differ diff --git a/content/images/rochacbruno/mongo.jpg b/content/images/rochacbruno/mongo.jpg new file mode 100644 index 000000000..48480caa4 Binary files /dev/null and b/content/images/rochacbruno/mongo.jpg differ diff --git a/content/images/rochacbruno/profiler.png b/content/images/rochacbruno/profiler.png new file mode 100644 index 000000000..c11f7cc79 Binary files /dev/null and b/content/images/rochacbruno/profiler.png differ diff --git a/content/images/rochacbruno/register.png b/content/images/rochacbruno/register.png new file mode 100644 index 000000000..8502d3c1f Binary files /dev/null and b/content/images/rochacbruno/register.png differ diff --git a/content/images/rochacbruno/robomongo.png b/content/images/rochacbruno/robomongo.png new file mode 100644 index 000000000..2b3e06010 Binary files /dev/null and b/content/images/rochacbruno/robomongo.png differ diff --git a/content/images/rochacbruno/security.jpg b/content/images/rochacbruno/security.jpg new file mode 100644 index 000000000..717f2ba0a Binary files /dev/null and b/content/images/rochacbruno/security.jpg differ diff --git a/content/images/rochacbruno/wtf_index.png b/content/images/rochacbruno/wtf_index.png new file mode 100644 index 000000000..f0ec82fe9 Binary files /dev/null and b/content/images/rochacbruno/wtf_index.png differ diff --git a/content/images/z4r4tu5tr4/lambda.jpg b/content/images/z4r4tu5tr4/lambda.jpg new file mode 100644 index 000000000..1b818652f Binary files /dev/null and b/content/images/z4r4tu5tr4/lambda.jpg differ diff --git a/content/images/z4r4tu5tr4/lambda.png b/content/images/z4r4tu5tr4/lambda.png new file mode 100644 index 000000000..426069c2d Binary files /dev/null and b/content/images/z4r4tu5tr4/lambda.png differ diff --git "a/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md" "b/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md" new file mode 100644 index 000000000..02be88830 --- /dev/null +++ "b/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md" @@ -0,0 +1,64 @@ +Title: Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04 +Slug: instalando-o-python-versão-3.7.0-alpha-1-no-ubuntu-16.04.md +Date: 2017-01-16 20:37:39 +Category: Python +Tags: python,tutorial,install +Author: Welton Vaz +Email: weltonvaz@gmail.com +Github: weltonvaz +Linkedin: welton-vaz-de-souza +Facebook: weltonv +Site: http://www.weltonvaz.com/ + + +Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04 + +A versão mais recente do Python, a 3.7.0 alfa 1, pode agora ser baixada ou clonada do GitHub facilmente. Uma das linguagens mais fáceis de usar e aprender, o Python foi criado nos anos 90 e é elogiado por sua fácil leitura de código e necessidade de poucas linhas de código, comparada a outras linguagens. Agora mais próxima da comunidade no Github! + +Depois disso os caminhos mudaram e conheci a profissão de Analista de Suporte e me ocupo disso desde então. Atualmente voltei a aprender uma linguagem, antes de mais nada, dei uma atualizada em lógica de programação, por sinal existem muitas boas apostilas e cursos gratuitos na Internet, dois caminhos muito bons. + +Sobre linguagem de programação, existem várias. Neste quesito comecei a conhecer a linguagem Python e logo me apaixonei pela simplicidade, beleza e eficiência. + +Depois disso tudo, você tem que instalar a linguagem em sua máquina. Por padrão, o Ubuntu 16.04 instala a versão 3.4, mas se você quiser, pode usar a versão 3.7.0a0 + +Obs.: Execute os comandos como root, ou usando o comando sudo no terminal. + +```shell +git clone https://github.com/python/cpython +cd cpython +apt-get install build-essential libssl-dev libffi-dev python3-dev +./configure +make +make test +make install + +# Se você quiser usar várias versões do Python 2.7, 3.6 e 3.7 use o comando abaixo +make altinstall +``` + +Observação: via apt instalei as dependências do python, no caso o openssl, porque o pip apresenta vários problemas com certificados na instalação dos módulos, mas, isso é para outro artigo + +Depois disso é só entrar no interpretador: + +```shell +python3.7 +``` + +Tela do interpretador Python + +```shell +Python 3.7.0a0 (default, Feb 16 2017, 18:59:44) +[GCC 5.4.0 20160609] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> + +``` +### Referências +Para ler mais sobre a linguagem: +* [Python] - Site oficial da Linguagem Python! +* [This is Python version 3.7.0 alpha 1] - Git da próxima versão do Python, hospedado no Github! +* [Python-Brasil] - A comunidade Python Brasil reune grupos de usuários em todo o Brasil interessados em difundir e divulgar a linguagem de programação. + +[Python]: +[Python-Brasil]: +[This is Python version 3.7.0 alpha 1]: \ No newline at end of file diff --git a/content/instalando-pycharm.md b/content/instalando-pycharm.md new file mode 100644 index 000000000..19938702e --- /dev/null +++ b/content/instalando-pycharm.md @@ -0,0 +1,88 @@ +Title: Instalando o PyCharm no Ubuntu (e irmãos) +Slug: instalando-pycharm-ubuntu +Date: 2015-06-14 12:58 +Tags: python,blog,tutorial,pycharm +Author: Erick Müller +Email: erick.muller@gmail.com +Github: ehriq +Bitbucket: ehriq +Twitter: ehriq +Category: Python + + +O objetivo aqui é instalar o PyCharm no Ubuntu e distribuições "irmãs" (como o Mint); estou instalando a versão **Community Edition**, que acredito que é a que muita gente que começa com essa poderosa IDE vai instalar pra dar os primeiros passos, experimentar. + +(aliás, bom avisar antes de começar: fiz o guia baseado no Ubuntu 14.04 e no Linux Mint 17.1; mas já fiz o mesmo procedimento em versões anteriores tanto do PyCharm quanto do Ubuntu, e com a versão "Professional" do PyCharm, e funcionou bem.) + + +## Parte 1 - instalar o Java + +As aplicações da JetBrains não são exatamente compatíveis com a versão do Java que vem por padrão no Ubuntu. Por isso, precisamos atualizar. + +Abra o terminal e execute os comandos abaixo: + +``` +sudo add-apt-repository ppa:webupd8team/java +sudo apt-get update +echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections +sudo apt-get install oracle-java8-installer -y +sudo apt-get install oracle-java8-set-default -y +``` + +Após os comandos acima, veja se a instalação está correta, executando no console: + +```bash +java -version +``` + +a saída esperada é algo como: + +``` +java version "1.8.0_45" +Java(TM) SE Runtime Environment (build 1.8.0_45-b14) +Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode) +``` + +## Parte 2 - pip e virtualenv + +O PyCharm usa o **pip** para baixar módulos/bibliotecas/extensões (como quiser chamar) do python, e o **virtualenv** para criar os queridos ambientes virtuais que mantém a sanidade dos programadores python. Então, para tirar proveito dessas funcionalidades, é bom garantir que estejam instalados também. + +Para isto, abra o console e: + +``` +cd ~/Downloads +wget -c https://bootstrap.pypa.io/get-pip.py +sudo -H python2 get-pip.py +sudo -H python3 get-pip.py +sudo -H pip2 install virtualenv +``` + + +## Parte 3 - copiar o PyCharm + +- clique no link ao lado para ir à página de [Download do PyCharm](https://www.jetbrains.com/pycharm/download/) +- clique em "Download Community" +- grave o arquivo no diretório que quiser + + + + +## Parte 4 - instalar o PyCharm + +Com os pré-requisitos prontos e instalados, vamos ao prato principal: + +``` +sudo tar -C /opt/ -xzf /pycharm-community-4.5.1.tar.gz +``` + +- Abra o navegador de arquivos e vá ao diretório */opt/pycharm-community-4.5.1* +- Entre no diretório 'bin' e, com dois cliques sobre, execute o script *'pycharm.sh'* +- Se aparecer uma janela perguntando como rodar o programa, clique no último botão (*'Executar'* ou *'Run'*) +- Dê "OK" na janela que abrir +- E na próxima janela, deixe todas as últimas opções selecionadas. Ao clicar em *'OK'* o PyCharm vai pedir a senha de 'root' para criar as entradas no menu. + +
+Tela de configuração final +
+ +Pronto, é isso. O software está instalado, e pronto para uso. diff --git a/content/integrando-django-com-cloudinary.md b/content/integrando-django-com-cloudinary.md new file mode 100644 index 000000000..edf095b6e --- /dev/null +++ b/content/integrando-django-com-cloudinary.md @@ -0,0 +1,219 @@ +Title: Integrando o Django com Cloudinary +Date: 2014-12-09 23:08 +Tags: Django, Cloudinary, Heroku +Category: deploy-infraestrutura +Slug: integrando-django-com-cloudinary +Author: Dyesten Paulon +Email: dyesten.pt@gmail.com +Github: dyesten +Facebook: dyesten.paulon + + + +### O que é? + +O Cloudinary é um serviço de gerenciamento de imagens e arquivos na nuvem, muito útil por exemplo para utilização junto ao Heroku, que não oferece o serviço de hospedagem de arquivos. Além de nos oferecer o serviço de hospedagem de imagens, o Cloudinary disponibiliza diversas manipulações, uso de efeitos, detecção facial e muitos outros recursos para as imagens enviadas. + +### O que é preciso? + +Para iniciarmos é preciso se __cadastrar__ no site. O cadastro pode ser feito com uma conta gratuita limitada. + +[Cadastro Gratuito](https://cloudinary.com/users/register/free) + +
+Ao finalizar o cadastro, uma tela como a exibida abaixo estará disponível. Atenção nos itens Cloud name, API Key e API Secret, eles serão úteis mais adiante. + +![Cadastro Cloudinary](/images/dyesten/cadastro_cloudinary.png) + +
+ +### Configurando o ambiente + +A instalação do pacote pode ser feita via __pip__: + + pip install cloudinary + +Ou baixando o pacote pelo [link](https://pypi.python.org/pypi/cloudinary/1.0.18) + + +### Configurando o settings +_Obs.: focaremos apenas nas configurações do cloudinary._ + +Primeiramente no INSTALLED_APPS incluiremos a linha __'cloudinary'__ e a linha com nossa app: + + INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'cloudinary', + 'cloudinary_example.core', + ) + +Ainda no settings adicione ao seu arquivo os parâmetros de configuração do Cloudinary: + +_Obs.: estes parâmetros são os mesmo da imagem inicial. E os abaixo apresentados são apenas ficticios._ + + CLOUDINARY = { + 'cloud_name' : seu_app_cloud, + 'api_key' : '00998877665544', + 'api_secret': 'DBseuAPPAKI-mtb7ZklCCBuJNoNetp' + } + +### Models + +Faremos a importação do Cloudinary e em seguida definiremos nossa classe __'Imagens'__: + + from django.db import models + from cloudinary.models import CloudinaryField + + class Imagens(models.Model): + imagem = CloudinaryField('imagem') + +_Obs.: execute o syncdb. No caso de utilização do South, acrescente o seguinte código:_ + + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ["^cloudinary\.models\.CloudinaryField"]) + +### Forms +Agora vamos importar o modelo em nosso __forms__, e definiremos nossa Classe em seguida: + + from django import forms + from cloudinary_example.core.models import Imagens + + class ImagensForm(forms.ModelForm): + class Meta: + model = Imagens + + +Agora vamos criar o formulário para fazermos o upload das imagens. +Antes vamos definir uma rota para nossa __views__, chamaremos ela de _'galeria'_: + + urlpatterns = patterns('cloudinary_example.core.views', + url(http://github.com/r'%5Egaleria/'),%20'galeria',%20name='galeria'), + ) + +Criaremos agora uma __views__ mais simples possível para chegar até nosso __template__: + + from django.shortcuts import render_to_response + from cloudinary_example.core.forms import ImagensForm + + def galeria(request): + return render_to_response('galeria.html', {'form':ImagensForm}) + + +Agora vamos criar nosso template _'galeria.html'_ com o seguinte código: + + {% extends 'base.html' %} + {% load cloudinary %} + + {% block content %} +
+ {% csrf_token %} + + + + + + + + + + +
+ + + {{ form.imagem }} +
+ +
+ {% endblock content%} + +![Template Inicial](/images/dyesten/template_inicial.png) + + +Legal mas nossa _views_ ainda não tem a ação para salvar a imagem no Cloudinary, agora vamos voltar e realizar a ação para salvar a imagem. +Primeiro vamos incluir as importações do nosso model. + + from cloudinary_example.core.models import Imagens + +Vamos alterar nosso método _'galeria'_ para o seguinte: + + from django.shortcuts import render + from cloudinary_example.core.forms import ImagensForm + from cloudinary_example.core.models import Imagens + + def galeria(request): + if request.method == 'POST': + form = ImagensForm(request.POST, request.FILES) + if form.is_valid(): + form.save() + + return render(request, 'galeria.html', {'form':ImagensForm}) + + +Ok, agora nossa imagem já pode ser salva no Cloudinary e nosso banco de dados. E como recuperar as imagens para exibição? +Neste exemplo vamos utilizar as mesma _views_ e o mesmo _template_ para exibição. Para isso vamos alterar nossa _views_ para buscar os id’s de nossas imagens salvos no banco, +altere o seu return para o seguinte: + + return render(request, 'galeria.html', {'form':ImagensForm, 'imgs':Imagens.objects.all()}) + +Já em seu template, adicione o seguinte código: + + + {% for img in imgs %} + + + + {% empty %} + + + + {% endfor %} +
{% cloudinary img.imagem %}
Sem Itens na Lista
+ +Com apenas este código acima, é possível buscarmos as imagens assim que carregadas. + +Alguns detalhes importantes: + +* A tag {% cloudinary img.imagem %} é equivalente a uma tag html : + + <_img src="http://res.cloudinary.com/suapasta/image/upload/v001122334455/codigodasuaimagem.jpg"> + +* Se é uma tag HTML, podemos utiliza-la sempre assim? A resposta é utilize a que se sentir mais confortável, não há qualquer problema. +* Alguns parâmetros podem ser adicionados a sua tag, como por exemplo, height, width, crop e muitos outros: + + {% cloudinary img.imagem height=500 width=400 crop="fill" %} + +Consulte a [documentação](http://cloudinary.com/documentation/django_image_manipulation) para mais exemplos de manipulação. + + +### Extra + +E como carregar múltiplos arquivos? O Django e Cloudinary te dão suporte total a essa ação de forma simples. + +Vamos começar alterando nosso __forms__. Primeiro adicionaremos a importação da biblioteca do Cloudinary: + + from cloudinary.forms import CloudinaryJsFileField + + +Em seguida, incluiremos após a class meta a linha que indica que nosso campo file input deve ser multiple: + + imagem = CloudinaryJsFileField( attrs={'multiple': 1, 'name':'imagens'} ) + + +E nossa view sofre uma pequena alteração para percorrer os itens do request Files: + + def galeria(request): + if request.method == 'POST': + form = ImagensForm(request.POST, request.FILES) + for f in request.FILES.getlist('imagens'): + Imagens(imagem=f).save() + + return render(request, 'galeria.html', {'form':ImagensForm, 'imgs':Imagens.objects.all()}) + +Tudo pronto, agora já temos um galeria simples. +O código está disponível no [GitHub](https://github.com/dyesten/cloudinary_example/). + diff --git a/content/introducao-classes-metodos-python-basico.rst b/content/introducao-classes-metodos-python-basico.rst index bc89c55eb..1182cfbf0 100644 --- a/content/introducao-classes-metodos-python-basico.rst +++ b/content/introducao-classes-metodos-python-basico.rst @@ -10,17 +10,21 @@ Introdução a Classes e Métodos em Python (básico) :github: rg3915 :summary: Abordaremos aqui o básico sobre o uso de classes e métodos e a manipulação de dados em Python. -Eu não sou a melhor pessoa do mundo para explicar este assunto, mas eu escrevi este post para introduzir um tópico sobre *manipulação de banco de dados em SQLite3 com Python*, porém mais informações sobre classes e métodos podem ser encontradas nos links abaixo. Veja os exemplos em `https://github.com/rg3915/pythonDesktopApp `_. +Eu não sou a melhor pessoa do mundo para explicar este assunto, mas vou tentar fazer uma breve introdução a classes e métodos em Python. + +Mais informações sobre classes e métodos podem ser encontradas nos links abaixo. Veja os exemplos em `https://github.com/rg3915/python-classes-metodos `_. + +> Este artigo foi atualizado em 26 de Maio de 2018. PS: *Considere a sintaxe para Python 3*. -Segundo a documentação do `Python `_ e o video `Python para Zumbis `_, uma **classe** associa dados (**atributos**) e operações (**métodos**) numa só estrutura. Um **objeto** é uma variável cujo tipo é uma classe, ou seja, um **objeto é uma instância** de uma classe. +Segundo a documentação do `Python `_ e o video `Python para Zumbis `_, uma **classe** associa dados (**atributos**) e operações (**métodos**) numa só estrutura. Um **objeto é uma instância** de uma classe. Ou seja, uma representação da classe. Por exemplo, Regis é uma instância de uma classe chamada Pessoa, mas a Pessoa é a classe que o representa de uma forma genérica. Se você criar um outro objeto chamado Fabio, esse objeto também será uma instancia da classe Pessoa. Na sua sintaxe mais elementar definimos uma classe conforme abaixo: .. code-block:: python - class NomeDaClasse(object): + class NomeDaClasse: pass E um método (função) como: @@ -42,15 +46,60 @@ Juntando os dois temos: .. code-block:: python - class NomeDaClasse(object): - atributo1 = None + class NomeDaClasse: + + def metodo(self, args): + pass + + + +A primeira pergunta que você vai ter é o porque do ``self`` em ``metodo``. A resposta curta é, todo metodo criado dentro de uma classe deve definir como primeiro parametro o ``self``. Para a resposta longa, por favor, leia a excelente explicação que o Pedro Werneck fez: `O porquê do self explícito em Python `_ + +A segunda pergunta é: para que serve o ``pass``? + +A resposta é que, em Python, ao contrario de várias outras liguagens de programação, os blocos de código **NÃO** são definidos com os caracteres ``{`` e ``}``, mas sim com indentação e o caractere ``:``. Devido a esse fato, python necessitava de algo para explicitar quando se quer definir um bloco vazio. O ``pass`` foi criado exatamente para explicitar essa situação. + +Um exemplo de uma função vazia feita em linguagem C e a mesma função vazia feita em Python: + +.. code-block:: C + + void metodo(int num){ + + } + +.. code-block:: python + + def metodo(num): + pass + +Importante: Note que para nome de **classes** usamos *PalavrasComeçandoPorMaiúscula* (isso tambem é conhecido como "`CapitalizeWords `_") e para nome de **métodos (funções)** usamos *minúsculas_separadas_por_underscore*. Esta é uma convenção adotada pelos *Pythonistas* segundo o `Guia de Estilo `_ **PEP 8** - `Style Guide for Python Code `_ escrito por `Guido Van Rossum `_. + + +Exemplo 0 - Pessoa +------------------ - def metodo(args): - pass +No exemplo mencionado no começo desse post o código mais simples seria o seguinte: -``pass`` significa que você pode escrever o seu código no lugar. E ``atributo1`` é um atributo com valor inicial ``None`` (nada). Poderia ser ``atributo1 = 0``, por exemplo. +.. code-block:: python + + class Pessoa: + + def __init__(self, nome): + self.nome = nome + + def __str__(self): + return self.nome + + + regis = Pessoa('Regis') + print(regis) + fabio = Pessoa('Fabio') + print(fabio) + +Note que ``regis`` é uma instância da classe ``Pessoa``, e ``fabio`` é uma outra instância. Ou seja, temos dois **objetos**: ``regis`` e ``fabio``. + +Os dois métodos serão explicados no próximo exemplo. -Importante: Note que para nome de **classes** usamos *PalavrasComeçandoPorMaiúscula* (isso tambem é conhecido como "CamelCase") e para nome de **métodos (funções)** usamos *minúsculas_separadas_por_underscore*. Esta é uma convenção adotada pelos *Pythonistas* segundo o `Guia de Estilo `_ **PEP 8** - `Style Guide for Python Code `_ escrito por `Guido Van Rossum `_. Exemplo 1 - Calculadora simples ------------------------------- @@ -60,7 +109,7 @@ Existem pelo menos duas formas diferentes de trabalhar com os parâmetros de ent .. code-block:: python #calculadora.py - class Calculadora(object): + class Calculadora: def __init__(self, a, b): self.a = a @@ -143,7 +192,7 @@ Agora faremos uma classe sem valor inicial e com **dois parâmetros** *para todo .. code-block:: python #calculadora2.py - class Calculadora(object): + class Calculadora: def soma(self, a, b): return a + b @@ -165,23 +214,28 @@ Usando o **terminal no modo interativo** façamos: >>> from calculadora2 import Calculadora >>> c = Calculadora() >>> print('Soma:', c.soma(2,3)) + Soma: 5 >>> print('Subtração:', c.subtrai(2,10)) + Subtração: -8 >>> print('Multiplicação:', c.multiplica(3,3)) + Multiplicação: 9 >>> print('Divisão:', c.divide(128,2)) + Divisão: 64.0 + >>> A vantagem de colocar os parâmetros em cada método, é que podemos calcular qualquer valor sem ter que instanciar uma nova classe para cada valor diferente. Exemplo 3 - Classe Pedido ------------------------- -Agora veremos um exemplo que mais se aproxima do que iremos fazer em banco de dados, mas aqui iremos apenas instanciar os objetos e armazená-los em memória numa lista. +Agora veremos um outro exemplo, mas aqui iremos apenas instanciar os objetos e armazená-los em memória numa lista. Veremos o código na íntegra e depois os comentários. .. code-block:: python #user.py - class User(object): + class User: seq = 0 objects = [] @@ -224,6 +278,9 @@ Podemos rodar o Python no modo `modo interativo >> u2 = User('Fabio',20) >>> u2.save() >>> print(User.all()) + [ + , + ] Agora os comentários: @@ -231,7 +288,7 @@ Definindo a classe .. code-block:: python - class User(object): + class User: Define um atributo que servirá como contador inicial e um atributo ``objects`` (tupla vazia) que é uma lista de instâncias de ``User`` que foram salvos (que chamaram o método ``save``). @@ -320,11 +377,63 @@ Note que nesse ``print`` a lista está vazia. Após chamar o ``save`` para as duas instâncias elas são guardadas e o método ``User.all()`` retorna essa lista. +Exemplo 4 - Televisão +--------------------- + +Escrevi mais um exemplo para fixar melhor o entendimento: `tv.py `_. + +.. code-block:: python + + class Televisao(): + + def __init__(self): + self.ligada = False + self.canal = 2 + + def muda_canal_para_baixo(self): + self.canal -= 1 + + def muda_canal_para_cima(self): + self.canal += 1 + + if __name__ == '__main__': + tv = Televisao() + print('Canal inicial:', tv.canal) + print('Ligada:', tv.ligada) + + tv.ligada = True + tv.canal = 5 + + print('Ligada:', tv.ligada) + print('Canal inicial:', tv.canal) + tv.muda_canal_para_cima() + print('Canal +', tv.canal) + tv.muda_canal_para_cima() + print('Canal +', tv.canal) + tv.muda_canal_para_baixo() + print('Canal -', tv.canal) + +Este programa está muito bem explicado no video `Python para Zumbis `_. + +A seguir o resultado do programa: + +.. code-block:: python + + $ python tv.py + ('Canal inicial:', 2) + ('Ligada:', False) + ('Ligada:', True) + ('Canal inicial:', 5) + ('Canal +', 6) + ('Canal +', 7) + ('Canal -', 6) + + Agradeço a colaboração de `Fabio Cerqueira `_. Veja os exemplos em `https://github.com/rg3915/pythonDesktopApp `_. -Mais informações em +Mais informações em `Classes Python `_ diff --git a/content/material-do-tutorial-web-scraping-na-nuvem.md b/content/material-do-tutorial-web-scraping-na-nuvem.md new file mode 100644 index 000000000..381c565b0 --- /dev/null +++ b/content/material-do-tutorial-web-scraping-na-nuvem.md @@ -0,0 +1,933 @@ +Title: Web Scraping na Nuvem com Scrapy +Date: 2015-11-13 10:04 +Author: Elias Dorneles +Slug: material-do-tutorial-web-scraping-na-nuvem +Tags: scrapy,spider,web-scraping,scraping,crawling,js2xml,extruct,web,scrapy-cloud,scrapinghub +Email: eliasdorneles@gmail.com +Github: eliasdorneles +Twitter: eliasdorneles +Site: http://eliasdorneles.github.io + + +Este tutorial foi apresentado na Python Brasil 2015 em São José dos Campos. + +## Roteiro + +* Introdução a web scraping com [Scrapy](http://scrapy.org/) +* Conceitos do Scrapy +* Hands-on: crawler para versões diferentes dum site de citações +* Rodando no [Scrapy Cloud](http://scrapinghub.com/platform/) + +O tutorial é 90% Scrapy e 10% Scrapy Cloud. + +> **Nota:** Scrapy Cloud é o serviço PaaS da Scrapinghub, a empresa em que +> trabalho e que é responsável pelo desenvolvimento do Scrapy. + +### Precisa de ajuda? + +Pergunte no [Stackoverflow em Português usando a tag +scrapy](http://pt.stackoverflow.com/tags/scrapy) ou pergunte em inglês no +[Stackoverflow em inglês](http://stackoverflow.com/tags/scrapy) ou na [lista de +e-mail scrapy-users](https://groups.google.com/forum/#!forum/scrapy-users). + + +## Introdução a web scraping com Scrapy + +### O que é Scrapy? + +[Scrapy](http://scrapy.org/) é um framework para crawlear web sites e extrair dados estruturados que +podem ser usados para uma gama de aplicações úteis (data mining, arquivamento, +etc). + +*Scraping:* +: extrair dados do conteúdo da página + +*Crawling:* +: seguir links de uma página a outra + +Se você já fez extração de dados de páginas Web antes em Python, são grandes as +chances de você ter usado algo como requests + beautifulsoup. Essas tecnologias +ajudam a fazer *scraping*. + +A grande vantagem de usar Scrapy é que tem suporte de primeira classe a +*crawling*. + +Por exemplo, ele permite configurar o tradeoff de **politeness vs speed** (sem +precisar escrever código pra isso) e já vem com uma configuração útil de +fábrica para crawling habilitada: suporte a cookies, redirecionamento tanto via +HTTP header quanto via tag HTML `meta`, tenta de novo requisições que falham, +evita requisições duplicadas, etc. + +Além disso, o framework é altamente extensível, permite seguir combinando +componentes e crescer um projeto de maneira gerenciável. + +### Instalando o Scrapy + +Recomendamos usar virtualenv, e instalar o Scrapy com: + + pip install scrapy + +A dependência chatinha é normalmente o [lxml](http://lxml.de/) (que precisa de +algumas bibliotecas C instaladas). Caso tenha dificuldade, [consulte as +instruções específicas por +plataforma](http://doc.scrapy.org/en/latest/intro/install.html#intro-install-platform-notes) +ou peça ajuda nos canais citados acima. + +Para verificar se o Scrapy está instalado corretamente, rode o comando: + + scrapy version + +A saída que obtenho rodando este comando no meu computador é: + + $ scrapy version + 2015-11-14 19:58:56 [scrapy] INFO: Scrapy 1.0.3 started (bot: scrapybot) + 2015-11-14 19:58:56 [scrapy] INFO: Optional features available: ssl, http11 + 2015-11-14 19:58:56 [scrapy] INFO: Overridden settings: {} + Scrapy 1.0.3 + + +### Rodando um spider + +Para ter uma noção inicial de como usar o Scrapy, vamos começar rodando um +spider de exemplo. + +Crie um arquivo **youtube_spider.py** com o seguinte conteúdo: + + + import scrapy + + + def first(sel, xpath): + return sel.xpath(xpath).extract_first() + + + class YoutubeChannelLister(scrapy.Spider): + name = 'channel-lister' + youtube_channel = 'portadosfundos' + start_urls = ['https://www.youtube.com/user/%s/videos' % youtube_channel] + + def parse(self, response): + for sel in response.css("ul#channels-browse-content-grid > li"): + yield { + 'link': response.urljoin(first(sel, './/h3/a/@href')), + 'title': first(sel, './/h3/a/text()'), + 'views': first(sel, ".//ul/li[1]/text()"), + } + + +Agora, rode o spider com o comando: + + scrapy runspider youtube_spider.py -o portadosfundos.csv + +O scrapy vai procurar um spider no arquivo **youtube_spider.py** e +escrever os dados no arquivo CSV **portadosfundos.csv**. + +Caso tudo deu certo, você verá o log da página sendo baixada, os dados sendo +extraídos, e umas estatísticas resumindo o processo no final, algo como: + + ... + 2015-11-14 20:14:21 [scrapy] DEBUG: Crawled (200) (referer: None) + 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos> + {'views': u'323,218 views', 'link': u'https://www.youtube.com/watch?v=qSqPkRi-UiE', 'title': u'GAR\xc7ONS'} + 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos> + {'views': u'1,295,054 views', 'link': u'https://www.youtube.com/watch?v=yXc8KCxyEyQ', 'title': u'SUCESSO'} + 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos> + {'views': u'1,324,448 views', 'link': u'https://www.youtube.com/watch?v=k9CbDcOT1e8', 'title': u'BIBLIOTECA'} + ... + {'downloader/request_bytes': 239, + 'downloader/request_count': 1, + 'downloader/request_method_count/GET': 1, + 'downloader/response_bytes': 27176, + 'downloader/response_count': 1, + 'downloader/response_status_count/200': 1, + 'item_scraped_count': 30, + ... + 2015-11-14 20:14:22 [scrapy] INFO: Spider closed (finished) + +Ao final, verifique os resultados abrindo o arquivo CSV no seu editor de +planilhas favorito. + +Se você quiser os dados em JSON, basta mudar a extensão do arquivo de saída: + + scrapy runspider youtube_spider.py -o portadosfundos.json + +Outro formato interessante que o Scrapy suporta é [JSON lines](http://jsonlines.org): + + scrapy runspider youtube_spider.py -o portadosfundos.jl + +Esse formato usa um item JSON em cada linha -- isso é muito útil para arquivos +grandes, porque fica fácil de concatenar dois arquivos ou acrescentar novas +entradas a um arquivo já existente. + + +## Conceitos do Scrapy + +## Spiders + +Conceito central no Scrapy, +[spiders](http://doc.scrapy.org/en/latest/topics/spiders.html) são classes que +herdam de +[``scrapy.Spider``](http://doc.scrapy.org/en/latest/topics/spiders.html#scrapy-spider), +definindo de alguma maneira as requisições iniciais do crawl e como proceder +para tratar os resultados dessas requisições. + +Um exemplo simples de spider é: + + import scrapy + + class SpiderSimples(scrapy.Spider): + name = 'meuspider' + + def start_requests(self): + return [scrapy.Request('http://example.com/')] + + def parse(self, response): + self.log('Visitei o site: %s' % response.url) + +Se você rodar o spider acima com o comando ``scrapy runspider``, deverá ver no +log as mensagens: + + 2015-11-14 21:11:13 [scrapy] DEBUG: Crawled (200) (referer: None) + 2015-11-14 21:11:13 [meuspider] DEBUG: Visitei o site: http://example.com + +Como iniciar um crawl a partir de uma lista de URLs é uma tarefa comum, +o Scrapy permite você usar o atribute de classe `start_urls` em vez de +definir o método ``start_requests()`` a cada vez: + + import scrapy + + class SpiderSimples(scrapy.Spider): + name = 'meuspider' + start_urls = ['http://example.com/'] + + def parse(self, response): + self.log('Visitei o site: %s' % response.url) + +## Callbacks e próximas requisições + +Repare o método ``parse()``, ele recebe um objeto *response* que representa uma +resposta HTTP, é o que chamamos de um **callback**. Os métodos **callbacks** no +Scrapy são +[generators](https://pythonhelp.wordpress.com/2012/09/03/generator-expressions/) +(ou retornam uma lista ou iterável) de objetos que podem ser: + +* dados extraídos (dicionários Python ou objetos que herdam de scrapy.Item) +* requisições a serem feitas a seguir (objetos scrapy.Request) + +O motor do Scrapy itera sobre os objetos resultantes dos callbacks e os +encaminha para o pipeline de dados ou para a fila de próximas requisições a +serem feitas. + +Exemplo: + + import scrapy + + class SpiderSimples(scrapy.Spider): + name = 'meuspider' + start_urls = ['http://example.com/'] + + def parse(self, response): + self.log('Visitei o site: %s' % response.url) + yield {'url': response.url, 'tamanho': len(response.body)} + + proxima_url = 'http://www.google.com.br/' + self.log('Agora vou para: %s' % proxima_url) + yield scrapy.Request(proxima_url, self.handle_google) + + def handle_google(self, response): + self.log('Visitei o google via URL: %s' % response.url) + + +Antes de rodar o código acima, experimente ler o código e prever +o que ele vai fazer. Depois, rode e verifique se ele fez mesmo +o que você esperava. + +Você deverá ver no log algo como: + + 2015-11-14 21:32:53 [scrapy] DEBUG: Crawled (200) (referer: None) + 2015-11-14 21:32:53 [meuspider] DEBUG: Visitei o site: http://example.com + 2015-11-14 21:32:53 [scrapy] DEBUG: Scraped from <200 http://example.com> + {'url': 'http://example.com/', 'tamanho': 1270} + 2015-11-14 21:32:53 [meuspider] DEBUG: Agora vou para: http://www.google.com.br + 2015-11-14 21:32:53 [scrapy] DEBUG: Crawled (200) (referer: http://example.com) + 2015-11-14 21:32:54 [meuspider] DEBUG: Visitei o google via URL: http://www.google.com.br + 2015-11-14 21:32:54 [scrapy] INFO: Closing spider (finished) + + +### Settings + +Outro conceito importante do Scrapy são as **settings** (isto é, configurações). +As **settings** oferecem uma maneira de configurar componentes do Scrapy, podendo +ser setadas de várias maneiras, tanto via linha de comando, variáveis de ambiente +em um arquivo **settings.py** no caso de você estar usando um projeto Scrapy ou ainda +diretamente no spider definindo um atributo de classe `custom_settings`. + +Exemplo setando no código do spider um delay de 1.5 segundos entre cada +requisição: + + class MeuSpider(scrapy.Spider): + name = 'meuspider' + + custom_settings = { + 'DOWNLOAD_DELAY': 1.5, + } + +Para setar uma setting diretamente na linha de comando com `scrapy runspider`, +use opção `-s`: + + scrapy runspider meuspider.py -s DOWNLOAD_DELAY=1.5 + +Uma setting útil durante o desenvolvimento é a *HTTPCACHE_ENABLED*, que +habilita uma cache das requisições HTTP -- útil para evitar baixar as +mesmas páginas várias vezes enquanto você refina o código de extração. + +> **Dica:** na versão atual do Scrapy, a cache por padrão só funciona caso você +> esteja dentro de um projeto, que é onde ele coloca um diretório +> `.scrapy/httpcache` para os arquivos de cache. Caso você queira usar a cache +> rodando o spider com `scrapy runspider`, você pode usar um truque "enganar" o +> Scrapy criando um arquivo vazio com o nome `scrapy.cfg` no diretório atual, e +> o Scrapy criará a estrutura de diretórios `.scrapy/httpcache` no diretório +> atual. + +Bem, por ora você já deve estar familiarizado com os conceitos importantes do +Scrapy, está na hora de partir para exemplos mais realistas. + + +## Hands-on: crawler para versões diferentes dum site de citações + +Vamos agora criar um crawler para um site de frases e citações, feito +para esse tutorial e disponível em: + +> *Nota:* O código-fonte do site está disponível em: +> + +### Descrição dos objetivos: + +O site contém uma lista de citações com autor e tags, paginadas com 10 citações +por páginas. Queremos obter todas as citações, juntamente com os respectivos +autores e lista de tags. + +Existem 4 variações do site, com o mesmo conteúdo mas usando markup HTML diferente. + +* Versão com markup HTML semântico: +* Versão com leiaute em tabelas: +* Versão com os dados dentro do código Javascript: +* Versão com AJAX e scroll infinito: + +Para ver as diferenças entre cada versão do site, acione a opção "Exibir +código-fonte" (Ctrl-U) do menu de contexto do seu +browser. + +> **Nota:** cuidado com a opção "Inspecionar elemento" do browser para inspecionar +> a estrutura do markup. Diferentemente do resultado da opção "Exibir +> código-fonte" Usando essa ferramenta, o código que você vê representa as +> estruturas que o browser cria para a página, e nem sempre mapeiam diretamente +> ao código HTML que veio na requisição HTTP (que é o que você obtém quando usa +> o Scrapy), especialmente se a página estiver usando Javascript ou AJAX. Outro +> exemplo é o elemento `` que é adicionado automaticamente pelos +> browsers em todas as tabelas, mesmo quando não declarado no markup. + + +### Spider para a versão com HTML semântico + +Para explorar a página (e a API de scraping do Scrapy), você pode usar +o comando `scrapy shell URL`: + + scrapy shell http://spidyquotes.herokuapp.com/ + +Esse comando abre um shell Python (ou [IPython](http://ipython.org), se você o +tiver instalado no mesmo virtualenv) com o objeto `response`, o mesmo que você +obteria num método **callback**. Recomendo usar o IPython porque fica mais fácil +de explorar as APIs sem precisar ter que abrir a documentação a cada vez. + +Exemplo de exploração com o shell: + + >>> # olhando o fonte HTML, percebi que cada citação está num
+ >>> # vamos pegar o primeiro dele, e ver como extrair o texto: + >>> quote = response.css('.quote')[0] + >>> quote + + “We accept the love we think we deserve.” + +
+ Tags: + + + inspirational + + love + +
+
+ >>> print quote.css('span').extract_first() + “We accept the love we think we deserve.” + >>> print quote.css('span::text').extract_first() # texto + “We accept the love we think we deserve.” + >>> quote.css('small::text').extract_first() # autor + u'Stephen Chbosky' + >>> + >>> # para a lista de tags, usamos .extract() em vez de .extract_first() + >>> quote.css('.tags a::text').extract() + [u'inspirational', u'love'] + >>> + + +Com o resultado da exploração inicial acima, podemos começar escrevendo um +spider assim, num arquivo `quote_spider.py`: + + import scrapy + + + class QuotesSpider(scrapy.Spider): + name = 'quotes' + start_urls = [ + 'http://spidyquotes.herokuapp.com/' + ] + + def parse(self, response): + for quote in response.css('.quote'): + yield { + 'texto': quote.css('span::text').extract_first(), + 'autor': quote.css('small::text').extract_first(), + 'tags': quote.css('.tags a::text').extract(), + } + +Se você rodar esse spider com: + + scrapy runspider quote_spider.py -o quotes.csv + +Você obterá os dados das citações da primeira página no arquivo `quotes.csv`. +Só está faltando agora seguir o link para a próxima página, o que você também +pode descobrir com mais alguma exploração no shell: + + >>> response.css('li.next') + [\n ] + >>> response.css('li.next a') + [Next ] + >>> response.css('li.next a::attr("href")').extract_first() + u'/page/2/' + >>> # o link é relativo, temos que joinear com a URL da resposta: + >>> response.urljoin(response.css('li.next a::attr("href")').extract_first()) + u'http://spidyquotes.herokuapp.com/page/2/' + +Juntando isso com o spider, ficamos com: + + import scrapy + + + class QuotesSpider(scrapy.Spider): + name = 'quotes' + start_urls = [ + 'http://spidyquotes.herokuapp.com/' + ] + + def parse(self, response): + for quote in response.css('.quote'): + yield { + 'texto': quote.css('span::text').extract_first(), + 'autor': quote.css('small::text').extract_first(), + 'tags': quote.css('.tags a::text').extract(), + } + link_next = response.css('li.next a::attr("href")').extract_first() + if link_next: + yield scrapy.Request(response.urljoin(link_next)) + +Agora, se você rodar esse spider novamente com: + + scrapy runspider quote_spider.py + +Perceberá que ainda assim ele vai extrair apenas os items da primeira página, e a segunda página +vai falhar com um código HTTP 429, com a seguinte mensagem no log: + + 2015-11-15 00:06:15 [scrapy] DEBUG: Crawled (429) (referer: http://spidyquotes.herokuapp.com/) + 2015-11-15 00:06:15 [scrapy] DEBUG: Ignoring response <429 http://spidyquotes.herokuapp.com/page/2/>: HTTP status code is not handled or not allowed + 2015-11-15 00:06:15 [scrapy] INFO: Closing spider (finished) + +
+ ![](http://httpstatusdogs.com/wp-content/uploads/2011/12/429.jpg) +
+ +O status HTTP 429 é usado para indicar que o servidor está recebendo muitas +requisições do mesmo cliente num curto período de tempo. + +No caso do nosso site, podemos simular o problema no próprio browser se +apertarmos o botão atualizar várias vezes no mesmo segundo: + +
+ ![](http://i.imgur.com/V3arr9E.jpg) +
+ +Neste caso, a mensagem no próprio site já nos diz o problema e a solução: o máximo de +requisições permitido é uma a cada segundo, então podemos resolver o problema setando +a configuração `DOWNLOAD_DELAY` para 1.5, deixando uma margem decente para podermos +fazer crawling sabendo que estamos respeitando a política. + +Como esta é uma necessidade comum para alguns sites, o Scrapy também permite +você configurar este comportamento diretamente no spider, setando o atributo de +classe `download_delay`: + + import scrapy + + + class QuotesSpider(scrapy.Spider): + name = 'quotes' + start_urls = [ + 'http://spidyquotes.herokuapp.com/' + ] + download_delay = 1.5 + + def parse(self, response): + for quote in response.css('.quote'): + yield { + 'texto': quote.css('span::text').extract_first(), + 'autor': quote.css('small::text').extract_first(), + 'tags': quote.css('.tags a::text').extract(), + } + link_next = response.css('li.next a::attr("href")').extract_first() + if link_next: + yield scrapy.Request(response.urljoin(link_next)) + +### Usando extruct para microdata + +Se você é um leitor perspicaz, deve ter notado que o markup HTML tem umas +marcações adicionais ao HTML normal, usando atributos `itemprop` e `itemtype`. +Trata-se de um mecanismo chamado +[Microdata](https://en.wikipedia.org/wiki/Microdata_(HTML)), [especificado pela +W3C](http://www.w3.org/TR/microdata/) e feito justamente para facilitar a +tarefa de extração automatizada. Vários sites suportam este tipo de marcação, +alguns exemplos famosos são [Yelp](http://www.yelp.com), [The +Guardian](http://www.theguardian.co.uk), [LeMonde](http://lemonde.fr), etc. + +Quando um site tem esse tipo de marcação para o conteúdo que você está +interessado, você pode usar o extrator de microdata da biblioteca +[extruct](https://pypi.python.org/pypi/extruct). + +Instale a biblioteca extruct com: + + pip install extruct + +Veja como fica o código usando a lib: + + import scrapy + from extruct.w3cmicrodata import LxmlMicrodataExtractor + + + class QuotesSpider(scrapy.Spider): + name = "quotes-microdata" + start_urls = ['http://spidyquotes.herokuapp.com/'] + download_delay = 1.5 + + def parse(self, response): + extractor = LxmlMicrodataExtractor() + items = extractor.extract(response.body_as_unicode(), response.url)['items'] + + for it in items: + yield it['properties'] + + link_next = response.css('li.next a::attr("href")').extract_first() + if link_next: + yield scrapy.Request(response.urljoin(link_next)) + +Usando microdata você reduz sobremaneira os problemas de mudanças de leiaute, +pois o desenvolvedor do site ao colocar o markup microdata se compromete a +mantê-lo atualizado. + +### Lidando com leiaute de tabelas: + +Agora, vamos extrair os mesmos dados mas para um markup faltando bom-gosto: + + +Para lidar com esse tipo de coisa, a dica é: **aprenda XPath**, vale a pena! + +Comece aqui: + +> *O domínio de XPath diferencia os gurus dos gafanhotos. -- Elias Dorneles, 2014* + +Como o markup HTML dessa página não uma estrutura boa, em vez de fazer scraping +baseado nas classes CSS ou ids dos elementos, com XPath podemos fazer baseando-se +na estrutura e nos padrões presentes no conteúdo. + +Por exemplo, se você abrir o shell para a página +, usando a expressão a seguir +retorna os os nós `tr` (linhas da tabela) que contém os textos das citações, +usando uma condição para pegar apenas linhas que estão imediatamente antes de +linhas cujo texto comece com `"Tags: "`: + + >>> response.xpath('//tr[./following-sibling::tr[1]/td[starts-with(., "Tags:")]]') + [\n '>, + \n '>, + \n '>, + \n '>, + \n '>, + \n '>, + \n '>, + \n '>, + \n '>, + \n '>] + +Para extrair os dados, precisamos de alguma exploração: + + >>> quote = response.xpath('//tr[./following-sibling::tr[1]/td[starts-with(., "Tags:")]]')[0] + >>> print quote.extract() + + “We accept the love we think we deserve.” Author: Stephen Chbosky + + >>> quote.xpath('string(.)').extract_first() + u'\n \u201cWe accept the love we think we deserve.\u201d Author: Stephen Chbosky\n ' + >>> quote.xpath('normalize-space(.)').extract_first() + u'\u201cWe accept the love we think we deserve.\u201d Author: Stephen Chbosky' + +Note como não tem marcação separando o autor do conteúdo, apenas uma string +"Author:". Então podemos usar o método `.re()` da classe seletor, que nos +permite usar uma expressão regular: + + >>> text, author = quote.xpath('normalize-space(.)').re('(.+) Author: (.+)') + >>> text + u'\u201cWe accept the love we think we deserve.\u201d' + >>> author + u'Stephen Chbosky' + +O código final do spider fica: + + import scrapy + + + class QuotesSpider(scrapy.Spider): + name = 'quotes-tableful' + start_urls = ['http://spidyquotes.herokuapp.com/tableful'] + download_delay = 1.5 + + def parse(self, response): + quotes_xpath = '//tr[./following-sibling::tr[1]/td[starts-with(., "Tags:")]]' + + for quote in response.xpath(quotes_xpath): + texto, autor = quote.xpath('normalize-space(.)').re('(.+) Author: (.+)') + tags = quote.xpath('./following-sibling::tr[1]//a/text()').extract() + yield dict(texto=texto, autor=autor, tags=tags) + + link_next = response.xpath('//a[contains(., "Next")]/@href').extract_first() + if link_next: + yield scrapy.Request(response.urljoin(link_next)) + + +Note como o uso de XPath permitiu vincularmos elementos de acordo com o conteúdo +tanto no caso das tags quanto no caso do link para a próxima página. + + +### Lidando com dados dentro do código + +Olhando o código-fonte da versão do site: +vemos que os dados que queremos estão todos num bloco de código Javascript, +dentro de um array estático. E agora? + +A dica aqui é usar a lib [js2xml](https://github.com/redapple/js2xml) para +converter o código Javascript em XML e então usar XPath ou CSS em cima do XML +resultante para extrair os dados que a gente quer. + +Instale a biblioteca js2xml com: + + pip install js2xml + +Exemplo no shell: + + scrapy shell http://spidyquotes.herokuapp.com/js/ + + >>> import js2xml + >>> script = response.xpath('//script[contains(., "var data =")]/text()').extract_first() + >>> sel = scrapy.Selector(_root=js2xml.parse(script)) + >>> sel.xpath('//var[@name="data"]/array/object') + ['>, + '>, + '>, + '>, + '>, + '>, + '>, + '>, + '>, + '>] + >>> quote = sel.xpath('//var[@name="data"]/array/object')[0] + >>> quote.xpath('string(./property[@name="text"])').extract_first() + u'\u201cWe accept the love we think we deserve.\u201d' + >>> quote.xpath('string(./property[@name="author"]//property[@name="name"])').extract_first() + u'Stephen Chbosky' + >>> quote.xpath('./property[@name="tags"]//string/text()').extract() + [u'inspirational', u'love'] + + +O código final fica: + + import scrapy + import js2xml + + + class QuotesSpider(scrapy.Spider): + name = 'quotes-js' + start_urls = ['http://spidyquotes.herokuapp.com/js/'] + download_delay = 1.5 + + def parse(self, response): + script = response.xpath('//script[contains(., "var data =")]/text()').extract_first() + sel = scrapy.Selector(_root=js2xml.parse(script)) + for quote in sel.xpath('//var[@name="data"]/array/object'): + yield { + 'texto': quote.xpath('string(./property[@name="text"])').extract_first(), + 'autor': quote.xpath( + 'string(./property[@name="author"]//property[@name="name"])' + ).extract_first(), + 'tags': quote.xpath('./property[@name="tags"]//string/text()').extract(), + } + + link_next = response.css('li.next a::attr("href")').extract_first() + if link_next: + yield scrapy.Request(response.urljoin(link_next)) + +Fica um pouco obscuro pela transformação de código Javascript em XML, mas a +extração fica mais confiável do que hacks baseados em expressões regulares. + +### Lidando com AJAX + +Agora, vamos para a versão AJAX com scroll infinito: + +Se você observar o código-fonte, verá que os dados não estão lá. No fonte só +tem um código Javascript que busca os dados via AJAX, você pode ver isso +olhando a aba *Network* das ferramentas do browser (no meu caso Chrome, mas +no Firefox é similar). + +Nesse caso, precisamos replicar essas requisições com o Scrapy, e tratar +os resultados de acordo com a resposta. + +Explorando no shell, vemos que o conteúdo é JSON: + + scrapy shell http://spidyquotes.herokuapp.com/api/quotes?page=1 + + >>> response.headers + {'Content-Type': 'application/json', + 'Date': 'Sun, 15 Nov 2015 22:18:29 GMT', + 'Server': 'gunicorn/19.3.0', + 'Via': '1.1 vegur'} + +Portanto, podemos simplesmente usar o módulo JSON da biblioteca padrão e ser feliz: + + >>> import json + >>> data = json.loads(response.body) + >>> data.keys() + [u'has_next', u'quotes', u'tag', u'page', u'top_ten_tags'] + >>> data['has_next'] + True + >>> data['quotes'][0] + {u'author': {u'goodreads_link': u'/author/show/12898.Stephen_Chbosky', + u'name': u'Stephen Chbosky'}, + u'tags': [u'inspirational', u'love'], + u'text': u'\u201cWe accept the love we think we deserve.\u201d'} + >>> data['page'] + 1 + +Código final do spider fica: + + import scrapy + import json + + + class QuotesSpider(scrapy.Spider): + name = 'quotes-scroll' + quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s' + start_urls = [quotes_base_url % 1] + download_delay = 1.5 + + def parse(self, response): + data = json.loads(response.body) + for d in data.get('quotes', []): + yield { + 'texto': d['text'], + 'autor': d['author']['name'], + 'tags': d['tags'], + } + if data['has_next']: + next_page = data['page'] + 1 + yield scrapy.Request(self.quotes_base_url % next_page) + +Ao lidar com requisições desse tipo, uma ferramenta útil que pode ser o +[minreq](https://pypi.python.org/pypi/minreq), instale com: ``pip install +minreq``. + +O minreq tenta encontrar a requisição mínima necessária para replicar +uma requisição do browser, e pode opcionalmente mostrar como montar +um objeto `scrapy.Request` equivalente. + +Rode o minreq com: + + minreq --action print_scrapy_request + +Ele fica esperando você colar uma requisição no formato cURL. Para isto, +encontre a requisição AJAX que você quer replicar na aba Network do browser, e +use o recurso "Copy as cURL": + + ![](http://i.imgur.com/hqz9b58.jpg) + +Cole no prompt do minreq, e espere ele fazer a mágica. =) + +> **Nota:** O minreq está em estágio pre-alpha, você provavelmente vai +> encontrar bugs -- por favor reporte no GitHub. + + +## Rodando no Scrapy Cloud + +O [Scrapy Cloud](http://scrapinghub.com/platform/) é a plataforma PaaS para +rodar crawlers na nuvem, o que permite evitar uma série de preocupações com +infraestrutura. + +Funciona como um "Heroku para crawlers", você faz deploy do seu projeto Scrapy +e configura jobs para rodar spiders periodicamente. Você pode também +configurar scripts Python para rodar periodicamente, os quais podem gerenciar o +escalonamento dos spiders. + +Comece criando uma conta free forever em: + +### Criação do projeto + +Até aqui nossos exemplos foram simplesmente rodando spiders com `scrapy runspider`. +Para fazer o deploy, chegou a hora de criar um projeto Scrapy propriamente dito. + +Para criar um projeto, basta rodar o comando `scrapy startproject` passando o nome do projeto: + + scrapy startproject quotes_crawler + +Feito isso, entre no diretório do projeto com `cd quotes_crawler` e copie os +arquivos com spiders para dentro do diretório `quotes_crawler/spiders`. +Certifique-se de usar um nome único para cada spider. + +A partir desse momento, você deve ser capaz de rodar cada spider em separado usando o comando: + + scrapy crawl NOME_DO_SPIDER + +> **Nota:** Dependendo do caso, é legal começar com um projeto desde o começo, +> para já fazer tudo de maneira estruturada. Pessoalmente, eu gosto de começar +> com spiders em arquivos soltos, quando estou apenas testando a viabilidade de +> um crawler. Crio um projeto apenas quando vou colaborar no código com outras +> pessoas ou fazer deploy no Cloud, nessa hora já é interessante que fique tudo +> estruturado e fácil de crescer dentro de um projeto. + +### Configuração no Scrapy Cloud + +Antes do deploy, você precisa criar um projeto no Scrapy Cloud. Na tela +inicial, clique no botão adicionar uma organização: + +
+ ![](http://i.imgur.com/9fsBv4I.png) +
+ +Dê um nome para a organização e confirme: + +
+ ![](http://i.imgur.com/GvfEXzu.png) +
+ +Em seguida, adicione um serviço do para hospedar o seu serviço, clicando no +botão "+ Service" que aparece dentro da organização criada: + +
+ ![](http://i.imgur.com/D0VTJLc.png) +
+ +Preencha os dados do seu projeto e confirme: + +
+ ![](http://i.imgur.com/05Hvbu3.png) +
+ +Depois disso, clique no nome do serviço na página inicial para acessar o local +onde seu projeto estará disponível: + +
+ ![](http://i.imgur.com/OIZLxYA.png) +
+ +Note o número identificador do seu projeto: você usará esse identificador na +hora fazer o deploy. + +
+ ![](http://i.imgur.com/ErsMJbB.png) +
+ + +### Instalando e configurando shub + +A maneira mais fácil de fazer deploy no Scrapy Cloud é usando a ferramenta +[shub](http://doc.scrapinghub.com/shub.html), cliente da linha de comando +para o Scrapy Cloud e demais serviços da Scrapinghub. + +Instale-a com: + + pip install shub --upgrade + +Faça login com o shub, usando o comando: + + shub login + +Informe sua API key conforme for solicitado ([descubra aqui sua API +key](https://dash.scrapinghub.com/account/apikey)). + +> **Dica:** Ao fazer login, o shub criará no arquivo `~/.netrc` uma entrada +> configurada para usar sua API key. Esse arquivo também é usado pelo `curl`, +> o que é útil para quando você deseje fazer requisições HTTP para as APIs na +> linha de comando. + + +### Preparando o projeto + +Antes de fazer deploy do projeto, precisamos fazer deploy das dependências no +Scrapy Cloud. +Crie um arquivo `requirements-deploy.txt` com o seguinte conteúdo: + + extruct + js2xml + slimit + ply + +Rode o comando: + + shub deploy-reqs PROJECT_ID requirements-deploy.txt + +Substitua `PROJECT_ID` pelo id do seu projeto (neste caso, 27199). + +#### Deploy das dependências + +Agora faça deploy do projeto com o comando: + + shub deploy -p PROJECT_ID + +Novamente, substituindo `PROJECT_ID` pelo id do seu projeto (neste caso, 27199) + +Se tudo deu certo, você verá algo como + + $ shub deploy -p 27199 + Packing version 1447628479 + Deploying to Scrapy Cloud project "27199" + {"status": "ok", "project": 27199, "version": "1447628479", "spiders": 5} + Run your spiders at: https://dash.scrapinghub.com/p/27199/ + +Agora você pode ir para a URL indicada (neste caso, ) +e agendar jobs dos spiders usando o botão "Schedule". + +> **Nota:** opcionalmente, você pode configurar o identificador do projeto no +> arquivo `scrapy.cfg`, para não precisar ter que lembrar a cada vez. + +Para configurar um spider para rodar periodicamente, utilize a aba "Periodic +Jobs", no menu à esquerda. + +# The End + +Era isso, se você chegou até aqui, parabéns e obrigado pela atenção! :) + +Você pode conferir o código do projeto final em: + +Para obter ajuda, pergunte no [Stackoverflow em Português usando a tag +scrapy](http://pt.stackoverflow.com/tags/scrapy) ou pergunte em inglês no +[Stackoverflow em inglês](http://stackoverflow.com/tags/scrapy) ou na [lista de +e-mail scrapy-users](https://groups.google.com/forum/#!forum/scrapy-users). + +Obrigado Valdir pela ajuda com a montagem desse tutorial, tanto no desenvolvimento +do app `spidyquotes` quanto na escrita do material. *You rock, dude!* diff --git a/content/microframework_contra_baterias_incluidas.rst b/content/microframework_contra_baterias_incluidas.rst new file mode 100644 index 000000000..c7f69ca5c --- /dev/null +++ b/content/microframework_contra_baterias_incluidas.rst @@ -0,0 +1,40 @@ +Microframework contra "Baterias Incluídas" +########################################## + +:date: 2015-02-17 12:35 +:tags: django, flask, python +:category: python +:slug: microframework-contra-baterias-incluidas +:author: Eduardo Klosowski +:email: eduardo_klosowski@yahoo.com +:github: eduardoklosowski +:site: https://eduardoklosowski.wordpress.com/ + +.. _Django: https://www.djangoproject.com/ +.. _Flask: http://flask.pocoo.org/ +.. _PHP: https://php.net/ +.. _Python: https://www.python.org/ +.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _WTForms: https://wtforms.readthedocs.org/en/latest/ + +Python_ é uma linguagem de programação que tem a fama existir mais frameworks web que palavras reservadas, isso se reflete em uma diversidade de opções para os mais diversos gostos. Talvez isso seja um reflexo da diversidade de pessoas que utilizam o Python para as mais diversas finalidades. + +Quando iniciei no desenvolvimento web, comecei com PHP_, da forma mais simples possível, montando cada página em arquivos separados com alguns includes para não repetir muito o código. Quando migrei para Python, o primeiro impacto foi ter que utilizar algum framework e não seguir a forma de cada página num arquivo, até poderia manter os arquivos utilizando CGI, mas não teria desempenho, ou escrever o WSGI diretamente, mas acabaria criando outro framework para facilitar o processo. + +Comecei a aprender o Django_, achei legal, porém foi complicado por ser muito diferente do que eu estava acostumado e passava mais tempo consultando a documentação que programando efetivamente. Com a filosofia de “baterias incluídas”, o Django tem incorporado bibliotecas para as funcionalidades mais comuns de uma página ou aplicação web pode precisar, como formulário, acesso a banco de dados relacionais, paginação, internacionalização… + +Outra opção que temos é utilizar um microframework, que auxilie apenas no WSGI e utilizar outras bibliotecas para montar a base da aplicação, como no caso do Flask_. Ele vem por padrão com outras bibliotecas, como o Jinja 2 para auxiliar a escrever o html das páginas e caso precise de banco de dados ou formulários, basta instalar alguma biblioteca como o SQLAlchemy_ e o WTForms_. + +A primeira coisa que pode ser notada ao comparar esses dois modelos é a complexidade, com certeza um microframework é mais simples e fácil de aprender, uma vez que você está focado apenas em como interagir com o servidor ou protocolo HTTP, não tem que se preocupar com banco de dados por exemplo. + +O primeiro ponto contra o microframework é a necessidade do programador conheçer ou procurar outras bibliotecas para partes específicas da aplicação, como persistência de dados. Muitas vezes isso pode levar ao desenvolvimento de algo que já está pronto e disponível por desconhecimento. Porém o programador não fica restrito ao que o framework suporta, podendo adotar qualquer tipo de biblioteca, diferente do Django que por exemplo não suporta oficialmente nenhum banco NoSQL, é possível utilizá-los, porém você não conseguirá integrá-los nativamente com os models e forms. Além de que utilizar algum framework específico pode aproveitar melhor as funcionalidades de um banco de dados, em vez de funções genéricas suportada por todos. + +Por outro lado, uma vantagem de você ter um framework que define as bibliotecas é a possibilidade de integração das mesmas, como no caso do Django, com um model escrito, é extremamente fácil criar um form baseado no mesmo, com validação, ou fazer a migração do esquema da tabela no banco sem precisar escrever tudo na mão ou duplicar o código e lógicas. Também não é necessário sair procurando bibliotecas na internet, e você terá tudo em apenas uma documentação, que na hora de buscar ajuda evita respostas do tipo com a biblioteca tal funciona ou ter que conhecer mais de uma biblioteca que fazem a mesma tarefa para decidir qual das duas utilizar. + +Microframeworks e “baterias incluídas” são abordagens opostas, cada uma pode se sair melhor que a outra de acordo com o caso. Se você tiver que desenvolver um sistema que necessite de bibliotecas que o Django oferece e se encaixe na forma dele de resolver os problemas, com certeza é uma ótima opção, uma vez que você terá as bibliotecas integradas e tudo pronto para utilizar. Caso o sistema seja mais simples, não necessitando de todas as bibliotecas oferecidas pelo Django, ou tenha necessidades mais específicas, o Flask começa a ganhar vantagens, uma vez que o fato de ser reduzido pode deixá-lo mais leve ou até ter uma configuração inicial mais rápida. + +Com certeza tem o conhecimento das duas abordagens é importante na hora da decisão do framework, nada pior que durante o desenvolvimento o framework ficar atrapalhando, por ele não ter foco para um determinado fim, ou ser tornar burocrático demais para coisas simples. Para quem estiver conhecendo um framework como o Django e acha que algumas coisas seriam mais práticas fazer na mão, tente visualizar todo o processo, que em algum ponto será facilitado por ser desta forma ou mais prático, porém vai necessitar de algum tempo para acostumar. + +---- + +Texto publicado originalmente no meu blog. Acesse https://eduardoklosowski.wordpress.com/ para mais textos como este. diff --git a/content/monitorando-ips-duplicados-na-rede.md b/content/monitorando-ips-duplicados-na-rede.md new file mode 100644 index 000000000..26b759366 --- /dev/null +++ b/content/monitorando-ips-duplicados-na-rede.md @@ -0,0 +1,119 @@ +Title: Monitorando Ips Duplicados na Rede +Slug: monitorando-ips-duplicados-na-rede +Date: 2018-05-15 10:24:00 +Category: Network +Tags: python,tutorial,network,scapy,defaultdict +Author: Silvio Ap Silva +Email: contato@kanazuchi.com +Github: kanazux +Linkedin: SilvioApSilva +Twitter: @kanazux +Site: http://kanazuchi.com + +Muitos administradores de redes e sysadmins encontram problemas de conectividade nos ambientes que administram e por muitas vezes o problema é um simples IP duplicado causando todo mal estar. Agora veremos como usar o scapy e defaultdict da lib collections para monitorar esses IPs. + +### Scapy + +O Scapy é uma poderosa biblioteca de manipulação de pacotes interativa, com abrangencia a uma enorme quantidade de protocolos provenientes da suite TCP/IP. +Mais informações sobre o scpay pode ser encontrada na [**documentação oficial**](http://scapy.readthedocs.io/en/latest/index.html). +Nesse caso em especifico iremos utilizar do scapy a metaclasse *ARP* e a função *sniff*. + +```python +from scapy.all import ARP, sniff +``` + +#### sniff + +Vamos usar a função sniff para monitorar os pacotes que trafegam na rede usando o protocolo ARP. +Pra isso vamos utilizar dela quatro parametros basicos: + +```python +sniff(prn=pacotes, filter="arp", iface=interface, timeout=10) +``` + +* prn, chama uma função para ser aplicada a cada pacote capturado pelo sniff. + +* filter, irá filtrar todos os pacotes que contiverem o protocolo ARP. + +* iface, determina a interface de rede que será monitorada. + +* timeout, irá determinar que nosso monitoramento da rede se dara por 60 segundos. + +#### ARP + +ARP é uma metaclasse de pacotes com dados sobre o protocolo arp pertencente a camada de enlace de dados. +Iremos utilizar essa metaclasse para filtrar os pacotes com informações de pacotes com respostas a requisições arp. (opcode == 2 [is at]) +As informações sobre o protocolo ARP podem serm encontradas na [rfc826](https://tools.ietf.org/html/rfc826) no site do IETF. + +### collections.defaultdict + +defaultdict é uma subclasse de dict que prove uma instancia de variavel para a chamada de uma chave inexistente. + +```python +from collections import defaultdict +list_ips = defaultdict(set) +``` + +Basicamente nossa função irá monitorar por um certo tempo o trafego de pacotes pela rede adicionar a nossa variavel *list_ips* o endereço ou endereços MAC encontrados. + +### Definindo a função que será passada como parametro para o sniff. + +Para cada pacote capturado pela função sniff, será checado se o opcode corresponde a um response do protocolo arp. +Caso seja, sera adicionado a nossa defaultdict. + +```python +def pacotes(pacote): + """Checa se o valor do opcode dentro do protocolo arp é igual a 2.""" + if pacote[ARP].op == 2: + # Se for adiciona o ip de origem e seu mac à dict list_ips + list_ips[pacote[ARP].psrc].add(pacote[ARP].hwsrc) +``` + +### Limpando a tabela arp + +Para que seja feita novas requisições arp, iremos limpar nossa tabela arp e iniciar o monitoramento da rede. +Pra isso iremos usar o comando arp, dentro do shell do sistema. *(Como uso [FreeBSD](https://www.freebsd.org) vou definir uma função chamando um comando pelo csh)* + +```python +import os +os.system('which arp') +/usr/sbin/arp +``` + +Com posse do caminho do comando arp, irei definir uma função que limpe a tabela e inicie o monitore a rede por 60 segundos. + +```python +def monitorar(interface): + """ + O comando arp no FreeBSD usa os parametros: + + -d para deletar as entradas + -i para declarar a interface + -a para representar todas entradas a serem deletas. + """ + cmd = "/usr/sbin/arp -d -i {} -a".format(interface) + os.system(cmd) + sniff(prn=pacotes, filter="arp", iface=interface, timeout=10) +``` + +E por ultimo chamar a função de monitoramento. +No meu caso eu vou monitorar a interface **em0**. + +```python +monitorar("em0") +``` + +Agora só conferir as entradas em nossa dict. + +```python +for ip in list_ips: + print "IP: {} -> MACs: {}".format(ip, ", ".join(list(list_ips[ip]))) + +IP: 192.168.213.1 -> MACs: 00:90:0b:49:3d:0a +IP: 192.168.213.10 -> MACs: 08:00:27:bf:52:6d, a0:f3:c1:03:74:6a +``` + +Eu uso um script rodando nos switchs e gateway da rede que me enviam mensagens assim que ips duplicados são encontrados na rede. +Também da pra usar o **arping** do scpay para fazer as requisições arp e coletar os responses. + +Abraços. diff --git a/content/n_a_1_em_20_minutos.rst b/content/n_a_1_em_20_minutos.rst new file mode 100644 index 000000000..50ff0c386 --- /dev/null +++ b/content/n_a_1_em_20_minutos.rst @@ -0,0 +1,435 @@ +Como otimizar suas consultas no Django - De N a 1 em 20 minutos +############################################################### + +:date: 2015-05-10 13:55 +:tags: python, django +:category: Python +:slug: django-introducao-queries +:author: Lucas Magnum +:email: lucasmagnumlopes@gmail.com +:github: lucasmagnum +:linkedin: lucasmagnum + + +Essa semana fiz uma palestra em um BEV no `Luizalabs `_. +Resolvi falar sobre Django, pois é um framework que utilizamos na empresa para diversos projetos. + +O objetivo é ensinar algumas técnicas simples e que auxiliam a diminuir o número de consultas que realizamos +no banco de dados.de + +Os slides podem acessados `aqui `_. + +Então, vamos lá! + + +Overview +-------- + +Geralmente nossa aplicação Django tem um arquivo ``models.py``, que contém nossa representação das tabelas no banco de dados. + +Para os próximos exemplos considere esse arquivo: + +.. code-block:: python + + from django.contrib.auth.models import User + from django.db import models + + class Cadastro(models.Model): + # chave estrangeira para o usuário + user = models.OneToOneField(User) + + # Outros campos + # [...] + + +Veja o exemplo abaixo, é muito comum ver algo parecido em algum tutorial sobre Django. + +.. code-block:: python + + >> Cadastro.objects.all() + +Mas o que realmente acontece quando fazemos isso? + +Para que a consulta aconteça, 5 elementos principais precisam interagir entre si. +Os elementos são: + +.. code-block:: python + + Model + Manager + QuerySet + Query + SQLCompiler + +É importante entender o papel de cada um, para que sejamos capazes de atuar com assertividade. + +**Model** + * É uma representação da nossa tabela de dados, contém os campos e os comportamentos dos dados que estamos armazenando. + +**Manager** + * Está sempre acoplado a um model e é responsável por expor os métodos do QuerySet. + Quando não declaramos nenhum manager, o Django cria por padrão o ``objects``. + +**QuerySet** + * QuerySet é um conjunto de ações que serão realizadas no banco de dados (select, insert, update ou delete). + Responsável por interagir diretamente com a Query. + +**Query** + * Cria uma estrutura de dados complexa com todos os elementos presentes em uma consulta. + Gera uma representação SQL de um QuerySet. + +**SQLCompiler** + * Recebe as instruções SQL e realiza as operações no banco de dados. + + +Agora que conhecemos os 5 elementos principais, vamos falar sobre **QuerySet**, é com ele +que vamos conseguir construir queries mais eficientes. + +QuerySets são Lazy +------------------ +Algo que é importante notar sobre o comportamento das QuerySets, são que elas são Lazy. + +Mas o que é isso? + +Imaginem as seguintes consultas: + +.. code-block:: python + + >> cadastros = Cadastro.objects.all() + >> ativos = cadastros.filter(ativo=True) + >> inativos = cadastros.filter(inativo=True) + +Sabe quantas consultas foram realizadas no banco de dados, por essas 3 linhas de código? NENHUMA. +QuerySets podem ser: + + * Construídas + * Filtradas + * Limitadas + * Ordenadas + * Passadas comoo parâmetro + +E nenhuma consulta será realizada no banco de dados. + +Quando dizemos que as QuerySets são lazy, queremos dizer que as consultas só serão realizadas no banco de dados, quando pedimos! + +Então, como pedimos? + +.. code-block:: python + + # Quando solicitamos somente um resultado + >> Cadastro.objects.all()[0] + + # Quando fazemos um slicing passando o parâmetro `step` + >> Cadastro.objects.all()[::2] + + # Quando fazemos uma iteração + >> [cadastro for cadastro in Cadastro.objects.all()] + + # Quando chamamos o método len() + >> len(Cadastro.objects.all()) + + # Quando chamamos o método list() + >> list(Cadastro.objects.all()) + + # Quando chamamos o método bool() + >> bool(Cadastro.objects.all()) + + # Quando chamamos o método repr() + >> repr(Cadastro.objects.all()) + + +Uma vez que entendemos como as consultas são realizadas no banco de dados, vamos aprender como resolver os problemas mais comuns quando se trata de consultas: relacionamentos. + + +Relacionamento OneToOne e ForeignKey +------------------------------------ + +OneToOne e ForeignKey são os tipos de relacionamentos mais comuns no Django, estamos utilizando-os quase intuitivamente. + +Imaginem o seguinte cenário: + +Temos um loop e a cada iteração invocamos um atributo do models que é uma chave estrangeira para outra tabela. + +.. code-block:: python + + >> cadastros = Cadastros.objects.all() + >> cadastros.count() + 500 # Temos 500 cadastros no nosso banco de dados + + # Fazemos uma iteração em todos os cadastros + >> for cadastro in cadastros: + # realizamos um print com o nome do usuário para tal cadastro. + # note que essa poderia ser qualquer outra operação, onde o atributo `user` fosse acessado + print cadastro.user + +Esse é um código simples e que geralmente não vemos problemas nenhum, mas iremos nos supreender +com quantas queries são realizadas no banco de dados. + +.. code-block:: python + + # https://docs.djangoproject.com/en/1.8/faq/models/#how-can-i-see-the-raw-sql-queries-django-is-running + >> from django.db import connection + + >> cadastros = Cadastros.objects.all() + + >> for cadastro in cadastros: + print cadastro.user + + >> print len(connection.queries) + 501 + +Foram realizadas **501** consultas para iterar sobre 500 cadastros (1 consulta para retornar todos os cadastros e 1 consulta para cada vez que acessamos o atributo ``user``). +Isso ocorre, porque estamos acessando um atributo que é um relacionamento para outra tabela, +cada vez que o Django acessa esse atributo uma nova consulta precisa ser realizada no banco de dados. + +Isso é válido tanto para OneToOne e ForeignKey. + +Como podemos resolver isso? Utilizando o método do QuerySet chamado ``select_related``. + +select_related +-------------- + +Veja o mesmo código sendo executado com `select_related `_. + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastros.objects.select_related('user').all() + + >> for cadastro in cadastros: + print cadastro.user + + >> print len(connection.queries) + 1 + +O objetivo do ``select_related`` é realizar uma única query que une todos os ``models`` relacionados. +Ele faz isso através de um ``JOIN`` na instrução ``SQL``, então realiza o cache do atributo para que possa acessá-lo sem realizar uma nova consulta. + +O único problema do ``select_related`` é que não funciona para campos **ManyToMany** e **Relacionamentos Reversos**, mas para esses casos temos o ``prefetch_related``. + +Primeiro, vamos entender o que é um relacionamento reverso. + +Relacionamento reverso +---------------------- + +Por padrão o Django adiciona um relacionamento reverso quando sua tabela é referenciada por uma chave estrangeira. + +Se não passar o parâmetro related_name, irá seguir o padrão _set + +.. code-block:: python + + from django.contrib.auth.models import User + from django.db import models + + class Cadastro(models.Model): + user = models.OneToOneField(User) + + # Outros campos + # [...] + + class Endereco(models.Model): + cadastro = models.ForeignKey(Cadastro) + + # Outros campos + # [...] + +Dessa forma, criamos um relacionamento reverso no model ``Cadastro``, quando referenciamos ele numa chave estrangeira no model ``Endereco``. + + +.. code-block:: python + + >> cadastros = Cadastro.objects.all() + + >> for cadastro in cadastros: + + # Uma vez que o relacionamento foi criado, podemos acessá-lo + print cadastro.endereco_set.all() + + +Se houvesse o parâmetro `related_name`, acessariamos pelo nome que criamos. + +.. code-block:: python + + class Endereco(models.Model): + cadastro = models.ForeignKey(Cadastro, related_name='enderecos') + + # Outros campos + # [...] + + + >> cadastros = Cadastro.objects.all() + >> for cadastro in cadastros: + # Acessando através do related_name + print cadastro.enderecos.all() + + +Relacionamentos reversos não são possíveis com o ``select_related``, por isso criou-se a partir da versão 1.4 o método ``prefetch_reĺated``. + + +prefetch_related +---------------- + +Ao acessar um **relacionamento reverso** ou atributo **ManyToMany**, assim como vimos para **OneToOne** e **ForeignKey**, uma nova consulta será realizada. + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastros.objects.all() + + >> for cadastro in cadastros: + print cadastro.enderecos.all() + + >> print len(connection.queries) + 501 + +Para esses casos, utilizamos o `prefetch_related `_, ela tem o comportamento similar ao ``select_related`` como diferença principal que o ``JOIN`` é realizado no ``Python``. + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastros.objects.prefetch_related('enderecos').all() + + >> for cadastro in cadastros: + print cadastro.enderecos.all() + + >> print len(connection.queries) + 2 + +Hum, 2 queries? Por quê? Porque o Django precisa realizar a primeira query para retornar todos os cadastros e uma query para retornar todos os endereços e então realizar o JOIN através do Python =) + +Legal, aprendemos a como diminuir o número de consultas que realizamos quando desejamos retirar alguma informação do banco de dados, mas e quando desejamos inserir, atualizar e deletar? + +Inserir dados +------------- + +Um problema para inserir dados é quando precisamos iterar sobre um conjunto grande de informações e criar um registro para cada linha, usos comum para importações e logs. + +.. code-block:: python + + >> from django.db import connection + >> nomes = [ + 'Lucas', 'Teste 01', 'Teste 02', 'Nome 3', # 1000 nomes no total + ] + + # Inserimos um cadastro para cada nome que existe na nossa variável `nomes` + >> for nome in nomes: + Cadastro.objects.create(nome=nome) + + >> print len(connection.queries) + 1000 + +E acessamos 1000 vezes o banco de dados para criar todos os cadastros. +Existe um método chamado ``bulk_create``, que resolve nosso problema. + +.. code-block:: python + + >> from django.db import connection + >> nomes = [ + 'Lucas', 'Teste 01', 'Teste 02', 'Nome 3', # 1000 nomes no total + ] + + >> cadastros = [] + >> for nome in nomes: + cadastro = Cadastro(nome=nome) + cadastros.append(cadastro) + + # Insere todos os cadastros de uma só vez + >> Cadastro.objects.bulk_create(cadastros) + >> print len(connection.queries) + 1 + +O **bulk_create** recebe uma lista de cadastros e cria realizando somente uma query. +É bom notar que cada item dentro da variável ``cadastros`` é uma representação do modelo de Cadastro. + + Não funciona para relacionamentos **ManyToMany** e que os ``signals`` do Django ``pre_save`` e ``post_save`` não serão chamados, + pois o método ``save`` não é utilizado nesse caso. + + +Atualizar dados +--------------- + +Muitas vezes precisamos atualizar um conjunto de dados e fazemos isso através de uma iteração sobre cada objeto e alterando o campo que desejamos. + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastro.objects.all() + + >> for cadastro in cadastros: + cadastro.notificado = True + cadastro.save() + + >> print len(connection.queries) + 501 # 1 consulta para retornar os cadastros e 1 para cada item no loop + + +E cada vez que chamamos o método ``save`` uma nova consulta é realizada. + +Para esses casos podemos utilizar o método ``update``. + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastro.objects.all() + + >> cadastros.update(notificado=True) + 500 # Retorna a quantidade de itens que foram atualizados + + >> print len(connection.queries) + 1 + + +O **update** realiza um **SQL Update** no banco de dados e retorna a quantidade de linhas que foram atualizados. + + Os ``signals`` do Django ``pre_save`` e ``post_save`` não serão chamados, + pois o método ``save`` não é utilizado nesse caso. + + +Deletar dados +--------------- + +O mesmo comportamento existe quando estamos removendo alguns dados. +Se fosse preciso apagar todos os dados, seria comum se alguém escrevesse assim: + +.. code-block:: python + + >> from django.db import connection + + >> cadastros = Cadastro.objects.all() + + >> for cadastro in cadastros: + cadastro.delete() + + >> print len(connection.queries) + 501 # 1 consulta para retornar os cadastros e 1 para cada item no loop + +Porém, pode-se fazer dessa maneira: + +.. code-block:: python + + >> from django.db import connection + + >> Cadastro.objects.all().delete() + + >> print len(connection.queries) + 1 + +QuerySet possui um método chamado **delete** que apaga todos os dados retornados. + +.. code-block:: python + + # Apagar somente inativos + >> Cadastro.objects.filter(inativo=True).delete() + + # Apagar somente ativos + >> Cadastro.objects.filter(ativo=True).delete() + +Deve-se lembrar, que assim como o **update** e o **bulk_create** os signals do Django não serão chamados, no caso do **delete** os signals são ``pre_delete`` e ``pos_delete``. + + +Espero que tenha ajudado, até a próxima! diff --git a/content/oo-de-outra-forma-1.md b/content/oo-de-outra-forma-1.md new file mode 100644 index 000000000..b2fb2d4d0 --- /dev/null +++ b/content/oo-de-outra-forma-1.md @@ -0,0 +1,202 @@ +Title: Orientação a objetos de outra forma: Classes e objetos +Slug: oo-de-outra-forma-1 +Date: 2021-04-12 15:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Nas poucas e raríssimas lives que eu fiz na [Twitch](https://www.twitch.tv/eduardoklosowski), surgiu a ideia de escrever sobre programação orientada a objetos em [Python](https://www.python.org/), principalmente por algumas diferenças de como ela foi implementada nessa linguagem. Aproveitando o tema, vou fazer uma série de postagens dando uma visão diferente sobre orientação a objetos. E nessa primeira postagem falarei sobre classes e objetos. + +## Usando um dicionário + +Entretanto, antes de começar com orientação a objetos, gostaria de apresentar e discutir alguns exemplos sem utilizar esse paradigma de programação. + +Pensando em um sistema que precise manipular dados de pessoas, é possível utilizar os [dicionários](https://docs.python.org/pt-br/3/library/stdtypes.html#mapping-types-dict) do Python para agrupar os dados de uma pessoa em uma única variável, como no exemplo a baixo: + +```python +pessoa = { + 'nome': 'João', + 'sobrenome': 'da Silva', + 'idade': 20, +} +``` + +Onde os dados poderiam ser acessados através da variável e do nome do dado desejado, como: + +```python +print(pessoa['nome']) # Imprimindo João +``` + +Assim, todos os dados de uma pessoa ficam agrupados em uma variável, o que facilita bastante a programação, visto que não é necessário criar uma variável para cada dado, e quando se manipula os dados de diferentes pessoas fica muito mais fácil identificar de qual pessoa aquele dado se refere, bastando utilizar variáveis diferentes. + +### Função para criar o dicionário + +Apesar de prático, é necessário replicar essa estrutura de dicionário toda vez que se desejar utilizar os dados de uma nova pessoa. Para evitar a repetição de código, a criação desse dicionário pode ser feita dentro de uma função que pode ser colocada em um módulo `pessoa` (arquivo, nesse caso com o nome de `pessoa.py`): + +```python +# Arquivo: pessoa.py + +def nova(nome, sobrenome, idade): + return { + 'nome': nome, + 'sobrenome': sobrenome, + 'idade': idade, + } +``` + +E para criar o dicionário que representa uma pessoa, basta importar esse módulo (arquivo) e chamar a função `nova`: + +```python +import pessoa + +p1 = pessoa.nova('João', 'da Silva', 20) +p2 = pessoa.nova('Maria', 'dos Santos', 18) +``` + +Desta forma, garante-se que todos os dicionários representando pessoas terão os campos desejados e devidamente preenchidos. + +### Função com o dicionário + +Também é possível criar algumas funções para executar operações com os dados desses dicionários, como pegar o nome completo da pessoa, trocar o seu sobrenome, ou fazer aniversário (o que aumentaria a idade da pessoa em um ano): + +```python +# Arquivo: pessoa.py + +def nova(nome, sobrenome, idade): + ... # Código abreviado + + +def nome_completo(pessoa): + return f"{pessoa['nome']} {pessoa['sobrenome']}" + + +def trocar_sobrenome(pessoa, sobrenome): + pessoa['sobrenome'] = sobrenome + + +def fazer_aniversario(pessoa): + pessoa['idade'] += 1 +``` + +E sendo usado como: + +```python +import pessoa + +p1 = pessoa.nova('João', 'da Silva', 20) +pessoa.trocar_sobrenome(p1, 'dos Santos') +print(pessoa.nome_completo(p1)) +pessoa.fazer_aniversario(p1) +print(p1['idade']) +``` + +Nesse caso, pode-se observar que todas as funções aqui implementadas seguem o padrão de receber o dicionário que representa a pessoa como primeiro argumento, podendo ter outros argumentos ou não conforme a necessidade, acessando e alterando os valores desse dicionário. + +## Versão com orientação a objetos + +Antes de entrar na versão orientada a objetos propriamente dita dos exemplos anteriores, vou fazer uma pequena alteração para facilitar o entendimento posterior. A função `nova` será separada em duas partes, a primeira que criará um dicionário, e chamará uma segunda função (`init`), que receberá esse dicionário como primeiro argumento (seguindo o padrão das demais funções) e criará sua estrutura com os devidos valores. + +```python +# Arquivo: pessoa.py + +def init(pessoa, nome, sobrenome, idade): + pessoa['nome'] = nome + pessoa['sobrenome'] = sobrenome + pessoa['idade'] = idade + + +def nova(nome, sobrenome, idade): + pessoa = {} + init(pessoa, nome, sobrenome, idade) + return pessoa + + +... # Demais funções do arquivo +``` + +Porém isso não muda a forma de uso: + +```python +import pessoa + +p1 = pessoa.nova('João', 'da Silva', 20) +``` + +### Função para criar uma pessoa + +A maioria das linguagens de programação que possuem o paradigma de programação orientado a objetos faz o uso de classes para definir a estrutura dos objetos. O Python também utiliza classes, que podem ser definidas com a palavra-chave `class` seguidas de um nome para ela. E dentro dessa estrutura, podem ser definidas funções para manipular os objetos daquela classe, que em algumas linguagens também são chamadas de métodos (funções declaradas dentro do escopo uma classe). + +Para converter o dicionário para uma classe, o primeiro passo é implementar uma função para criar a estrutura desejada. Essa função deve possui o nome `__init__`, e é bastante similar a função `init` do código anterior: + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade +``` + +As diferenças são que agora o primeiro parâmetro se chama `self`, que é um padrão utilizado no Python, e em vez de usar colchetes e aspas para acessar os dados, aqui basta utilizar o ponto e o nome do dado desejado (que aqui também pode ser chamado de atributo, visto que é uma variável do objeto). A função `nova` implementada anteriormente não é necessária, a própria linguagem cria um objeto e passa ele como primeiro argumento para o `__init__`. E assim para se criar um objeto da classe `Pessoa` basta chamar a classe como se fosse uma função, ignorando o argumento `self` e informando os demais, como se estivesse chamando a função `__init__` diretamente: + +```python +p1 = Pessoa('João', 'da Silva', 20) +``` + +Nesse caso, como a própria classe cria um contexto diferente para as funções (escopo ou *namespace*), não está mais sendo utilizado arquivos diferentes, porém ainda é possível fazê-lo, sendo necessário apenas fazer o `import` adequado. Mas para simplificação, tanto a declaração da classe, como a criação do objeto da classe `Pessoa` podem ser feitas no mesmo arquivo, assim como os demais exemplos dessa postagem. + +### Outras funções + +As demais funções feitas anteriormente para o dicionário também podem ser feitas na classe `Pessoa`, seguindo as mesmas diferenças já apontadas anteriormente: + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + + def nome_completo(self): + return f'{self.nome} {self.sobrenome}' + + def trocar_sobrenome(self, sobrenome): + self.sobrenome = sobrenome + + def fazer_aniversario(self): + self.idade += 1 +``` + +Para se chamar essas funções, basta acessá-las através do contexto da classe, passando o objeto criado anteriormente como primeiro argumento: + +```python +p1 = Pessoa('João', 'dos Santos', 20) +Pessoa.trocar_sobrenome(p1, 'dos Santos') +print(Pessoa.nome_completo(p1)) +Pessoa.fazer_aniversario(p1) +print(p1.idade) +``` + +Essa sintaxe é bastante semelhante a versão sem orientação a objetos implementada anteriormente. Porém quando se está utilizando objetos, é possível chamar essas funções com uma outra sintaxe, informando primeiro o objeto, seguido de ponto e o nome da função desejada, com a diferença de que não é mais necessário informar o objeto como primeiro argumento. Como a função foi chamada através de um objeto, o próprio Python se encarrega de passá-lo para o argumento `self`, sendo necessário informar apenas os demais argumentos: + +```python +p1.trocar_sobrenome('dos Santos') +print(p1.nome_completo()) +p1.fazer_aniversario() +print(p1.idade) +``` + +Existem algumas diferenças entre as duas sintaxes, porém isso será tratado posteriormente. Por enquanto a segunda sintaxe pode ser vista como um [açúcar sintático](https://pt.wikipedia.org/wiki/A%C3%A7%C3%BAcar_sint%C3%A1tico) da primeira, ou seja, uma forma mais rápida e fácil de fazer a mesma coisa que a primeira, e por isso sendo a recomendada. + +## Considerações + +Como visto nos exemplos, programação orientada a objetos é uma técnica para juntar variáveis em uma mesma estrutura e facilitar a escrita de funções que seguem um determinado padrão, recebendo a estrutura como argumento, porém a sintaxe mais utilizada no Python para chamar as funções de um objeto (métodos) posiciona a variável que guarda a estrutura antes do nome da função, em vez do primeiro argumento. + +No Python, o argumento da estrutura ou objeto (`self`) aparece explicitamente como primeiro argumento da função, enquanto em outras linguagens essa variável pode receber outro nome (como `this`) e não aparece explicitamente nos argumentos da função, embora essa variável tenha que ser criada dentro do contexto da função para permitir manipular o objeto. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/oo-de-outra-forma-2.md b/content/oo-de-outra-forma-2.md new file mode 100644 index 000000000..2bcf6e9e7 --- /dev/null +++ b/content/oo-de-outra-forma-2.md @@ -0,0 +1,104 @@ +Title: Orientação a objetos de outra forma: Métodos estáticos e de classes +Slug: oo-de-outra-forma-2 +Date: 2021-04-19 17:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Na [postagem anterior](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-classes-e-objetos-3mfd) foi apresentado o `self`, nessa postagem será discutido mais a respeito desse argumento, considerando opções para ele e suas aplicações. + +## Métodos estáticos + +Nem todas as funções de uma classe precisam receber uma referência de um objeto para lê-lo ou alterá-lo, muitas vezes uma função pode fazer o seu papel apenas com os dados passados como argumento, por exemplo, receber um nome e validar se ele possui pelo menos três caracteres sem espaço. Dessa forma, essa função poderia ser colocada fora do escopo da classe, porém para facilitar sua chamada, e possíveis alterações (que será discutido em outra postagem), é possível colocar essa função dentro da classe e informar que ela não receberá o argumento `self` com o decorador `@staticmethod`: + +```python +class Pessoa: + ... # Demais funções + + @staticmethod + def valida_nome(nome): + return len(nome) >= 3 and ' ' not in nome +``` + +Dessa forma, essa função pode ser chamada diretamente de um objeto pessoa, ou até mesmo diretamente da classe, sem precisar criar um objeto primeiro: + +```python +# Chamando diretamente da classe +print(Pessoa.valida_nome('João')) + +# Chamando através de um objeto do tipo Pessoa +p1 = Pessoa('João', 'da Silva', 20) +print(p1.valida_nome(p1.nome)) +``` + +E essa função também pode ser utilizada dendro de outras funções, como validar o nome na criação de uma pessoa, de forma que caso o nome informado seja válido, será criado um objeto do tipo Pessoa, e caso o nome seja inválido, será lançado uma exceção: + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + if not self.valida_nome(nome): + raise ValueError('Nome inválido') + + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + + ... # Demais funções + + @staticmethod + def valida_nome(nome): + return len(nome) >= 3 and ' ' not in nome + + +p1 = Pessoa('João', 'da Silva', 20) # Cria objeto +p2 = Pessoa('a', 'da Silva', 20) # Lança ValueError: Nome inválido +``` + +## Métodos da classe + +Entretanto algumas funções podem precisar de um meio termo, necessitar acessar o contexto da classe, porém sem necessitar de um objeto. Isso é feito através do decorador `@classmethod`, onde a função decorada com ele, em vez de receber um objeto como primeiro argumento, recebe a própria classe. + +Para demonstrar essa funcionalidade será implementado um *id* auto incremental para os objetos da classe `Pessoa`: + +```python +class Pessoa: + total_de_pessoas = 0 + + @classmethod + def novo_id(cls): + cls.total_de_pessoas += 1 + return cls.total_de_pessoas + + def __init__(self, nome, sobrenome, idade): + self.id = self.novo_id() + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + +p1 = Pessoa('João', 'da Silva', 20) +print(p1.id) # Imprime 1 +p2 = Pessoa('Maria', 'dos Santos', 18) +print(p2.id) # Imprime 2 +print(Pessoa.total_de_pessoas) # Imprime 2 +print(p1.total_de_pessoas) # Imprime 2 +print(p2.total_de_pessoas) # Imprime 2 +``` + +Nesse código é criado uma variável `total_de_pessoas` dentro do escopo da classe `Pessoas`, e que é compartilhado tanto pela classe, como pelos objetos dessa classe, diferente de declará-la com `self.` dentro do `__init__`, onde esse valor pertenceria apenas ao objeto, e não é compartilhado com os demais objetos. Declarar variáveis dentro do contexto da classe é similar ao se declarar variáveis com `static` em outras linguagens, assim como o `@classmethod` é semelhante a declaração de funções com `static`. + +As funções declaradas com `@classmethod` também podem ser chamadas sem a necessidade de se criar um objeto, como `Pessoa.novo_id()`, embora que para essa função específica isso não faça muito sentido, ou receber outros argumentos, tudo depende do que essa função fará. + +## Considerações + +Embora possa parecer confuso identificar a diferença de uma função de um objeto (função sem decorador), função de uma classe (com decorador `@classmethod`) e função sem acesso a nenhum outro contexto (com decorador `@staticmethod`), essa diferença fica mais clara ao se analisar o primeiro argumento recebido por cada tipo de função. Podendo ser a referência a um objeto (`self`) e assim necessitando que um objeto seja criado anteriormente, ser uma classe (`cls`) e não necessitando receber um objeto, ou simplesmente não recebendo nenhum argumento especial, apenas os demais argumentos necessários para a função. Sendo diferenciados pelo uso dos decoradores. + +Na orientação a objetos implementada pelo Python, algumas coisas podem ficar confusas quando se mistura com nomenclaturas de outras linguagens que possuem implementações diferentes. A linguagem Java, por exemplo, utiliza a palavra-chave `static` para definir os atributos e métodos de classe, enquanto no Python um método estático é aquele que não acessa nem um objeto, nem uma classe, devendo ser utilizado o escopo da classe e o decorador `@classmethod` para se criar atributos e métodos da classe. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/oo-de-outra-forma-3.md b/content/oo-de-outra-forma-3.md new file mode 100644 index 000000000..0b1d55ace --- /dev/null +++ b/content/oo-de-outra-forma-3.md @@ -0,0 +1,225 @@ +Title: Orientação a objetos de outra forma: Herança +Slug: oo-de-outra-forma-3 +Date: 2021-04-26 17:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Algo que ajuda no desenvolvimento é a reutilização de código. Em orientação a objetos, essa reutilização pode ocorrer através de herança, onde um objeto pode se comportar como um objeto da sua própria classe, como também da classe que herdou. + +## Adicionando funcionalidades + +Uma das utilidades da herança é estender uma classe para adicionar funcionalidades. Pensando no contexto das postagens anteriores, poderíamos querer criar um usuário e senha para algumas pessoas poderem acessar o sistema. Isso poderia ser feito adicionando atributos usuário e senha para as pessoas, além de uma função para validar se os dados estão corretos, e assim permitir o acesso ao sistema. Porém isso não pode ser feito para todas as pessoas, e sim apenas para aqueles que possuem permissão de acesso. + +### Sem orientação a objetos + +Voltando a solução com dicionários (sem utilizar orientação a objetos), isso consistiria em criar um dicionário com a estrutura de uma pessoa, e em seguida estender essa estrutura com os novos campos de usuário e senha nesse mesmo dicionário, algo como: + +```python +# Arquivo: pessoa.py + +def init(pessoa, nome, sobrenome, idade): + pessoa['nome'] = nome + pessoa['sobrenome'] = sobrenome + pessoa['idade'] = idade + + +def nome_completo(pessoa): + return f"{pessoa['nome']} {pessoa['sobrenome']}" +``` + +```python +# Arquivo: pessoa_autenticavel.py + +def init(pessoa, usuario, senha): + pessoa['usuario'] = usuario + pessoa['senha'] = senha + + +def autenticar(pessoa, usuario, senha): + return pessoa['usuario'] == usuario and pessoa['senha'] == senha +``` + +```python +import pessoa +import pessoa_autenticavel + +p = {} +pessoa.init(p, 'João', 'da Silva', 20) +pessoa_autenticavel.init(p, 'joao', 'secreta') + +print(pessoa.nome_completo(p)) +print(pessoa_autenticavel.autenticar(p, 'joao', 'secreta')) +``` + +Porém nessa solução é possível que o programador esqueça de chamar as duas funções `init` diferentes, e como queremos que todo dicionário com a estrutura de `pessoa_autenticavel` contenha também a estrutura de `pessoa`, podemos chamar o `init` de pessoa dentro do `init` de `pessoa_autenticavel`: + +```python +# Arquivo: pessoa_autenticavel.py + +import pessoa + + +def init(p, nome, sobrenome, idade, usuario, senha): + pessoa.init(p, nome, sobrenome, idade) + p['usuario'] = usuario + p['senha'] = senha + + +... # Demais funções +``` + +```python +import pessoa +import pessoa_autenticavel + +p = {} +pessoa_autenticavel.init(p, 'João', 'da Silva', 20, 'joao', 'secreta') + +print(pessoa.nome_completo(p)) +print(pessoa_autenticavel.autenticar(p, 'joao', 'secreta')) +``` + +Nesse caso foi necessário alterar o nome do argumento `pessoa` da função `pessoa_autenticavel.init` para não conflitar com o outro módulo importado com esse mesmo nome. Porém ao chamar um `init` dentro de outro, temos a garantia de que o dicionário será compatível tanto com a estrutura pedida para ser criada pelo programador, quanto pelas estruturas pais dela. + +### Com orientação a objetos + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + + def nome_completo(self): + return f'{self.nome} {self.sobrenome}' + + +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + Pessoa.__init__(self, nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha + + +p = PessoaAutenticavel('João', 'da Silva', 20, 'joao', 'secreta') + +print(Pessoa.nome_completo(p)) +print(PessoaAutenticavel.autenticar(p, 'joao', 'secreta')) +``` + +A principal novidade desse exemplo é que ao declarar a classe `PessoaAutenticavel` (filha), foi declarado a classe `Pessoa` (pai) entre parênteses, isso faz o interpretador Python criar uma cópia dessa classe estendendo-a com as novas funções que estamos criando. Porém pode ser um pouco redundante chamar `Pessoa.__init__` dentro da função `__init__` sendo que já foi declarado que ela estende `Pessoa`, podendo ser trocado por `super()`, que aponta para a classe que foi estendida. Exemplo: + +```python +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + super().__init__(nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + ... # Demais funções +``` + +Assim se evita repetir o nome da classe, e já passa automaticamente a referência para `self`, assim como quando usamos o açúcar sintático apresentado na primeira postagem dessa série. E esse açúcar sintática também pode ser usado para chamar tanto as funções declaradas em `Pessoa` quanto em `PessoaAutenticavel`. Exemplo: + +```python +p = PessoaAutenticavel('João', 'da Silva', 20, 'joao', 'secreta') + +print(p.nome_completo()) +print(p.autenticar('joao', 'secreta')) +``` + +Esse método também facilita a utilização das funções, uma vez que não é necessário lembrar em qual classe que cada função foi declarada. Na verdade, como `PessoaAutenticavel` estende `Pessoa`, seria possível executar também `PessoaAutenticavel.nome_completo`, porém eles apontam para a mesma função. + +## Sobrescrevendo uma função + +A classe `Pessoa` possui a função `nome_completo` que retorna uma `str` contento nome e sobrenome. Porém no Japão, assim como em outros países asiáticos, o sobrenome vem primeiro, e até [estão pedindo para seguir a tradição deles ao falarem os nomes de japoneses](https://noticias.uol.com.br/ultimas-noticias/efe/2019/06/24/japao-quer-voltar-a-ordem-tradicional-dos-nomes-abe-shinzo-nao-shinzo-abe.htm), como o caso do primeiro-ministro, mudando de Shinzo Abe para Abe Shinzo. + +### Com orientação a objetos + +Isso também pode ser feito no sistema usando herança, porém em vez de criar uma nova função com outro nome, é possível criar uma função com o mesmo nome, sobrescrevendo a anterior, porém apenas para os objetos da classe filha. Algo semelhante ao que já foi feito com a função `__init__`. Exemplo: + +```python +class Japones(Pessoa): + def nome_completo(self): + return f'{self.sobrenome} {self.nome}' + + +p1 = Pessoa('João', 'da Silva', 20) +p2 = Japones('Shinzo', 'Abe', 66) + +print(p1.nome_completo()) # João da Silva +print(p2.nome_completo()) # Abe Shinzo +``` + +Essa relação de herança traz algo interessante, todo objeto da classe `Japones` se comporta como um objeto da classe `Pessoa`, porém a relação inversa não é verdade. Assim como podemos dizer que todo japonês é uma pessoa, mas nem todas as pessoas são japonesas. Ser japonês é um caso mais específico de pessoa, assim como as demais nacionalidades. + +### Sem orientação a objetos + +Esse comportamento de sobrescrever a função `nome_completo` não é tão simples de replicar em uma estrutura de dicionário, porém é possível fazer. Porém como uma pessoa pode ser tanto japonês quanto não ser, não é possível saber de antemão para escrever no código `pessoa.nome_completo` ou `japones.nome_completo`, que diferente do exemplo da autenticação, agora são duas funções diferentes, isso precisa ser descoberto dinamicamente quando se precisar chamar a função. + +Uma forma de fazer isso é guardar uma referência para a função que deve ser chamada dentro da própria estrutura. Exemplo: + +```python +# Arquivo: pessoa.py + +def init(pessoa, nome, sobrenome, idade): + pessoa['nome'] = nome + pessoa['sobrenome'] = sobrenome + pessoa['idade'] = idade + pessoa['nome_completo'] = nome_completo + + +def nome_completo(pessoa): + return f"{pessoa['nome']} {pessoa['sobrenome']}" +``` + +```python +# Arquivo: japones.py + +import pessoa + + +def init(japones, nome, sobrenome, idade): + pessoa(japones, nome, sobrenome, idade) + japones['nome_completo'] = nome_completo + + +def nome_completo(japones): + return f"{pessoa['sobrenome']} {pessoa['nome']}" +``` + +```python +import pessoa +import japones + +p1 = {} +pessoa.init(p1, 'João', 'da Silva', 20) +p2 = {} +japones.init(p2, 'Shinzo', 'Abe', 66) + +print(p1['nome_completo'](p1)) # João da Silva +print(p2['nome_completo'](p2)) # Abe Shinzo +``` + +Perceba que a forma de chamar a função foi alterada. O que acontece na prática é que toda função que pode ser sobrescrita não é chamada diretamente, e sim a partir de uma referência, e isso gera um custo computacional adicional. Como esse custo não é tão alto (muitas vezes sendo quase irrelevante), esse é o comportamento adotado em várias linguagens, porém em C++, por exemplo, existe a palavra-chave `virtual` para descrever quando uma função pode ser sobrescrita ou não. + +## Considerações + +Herança é um mecanismo interessante para ser explorado com o objetivo de reaproveitar código e evitar repeti-lo. Porém isso pode vir com alguns custos, seja computacional durante sua execução, seja durante a leitura do código, sendo necessário verificar diversas classes para saber o que de fato está sendo executado, porém isso também pode ser usado para ocultar e abstrair lógicas mais complicadas, como eu já comentei em outra [postagem](https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e). + +Herança também permite trabalhar com generalização e especialização, podendo descrever o comportamento mais geral, ou mais específico. Ou simplesmente só adicionar mais funcionalidades a uma classe já existente. + +Assim como foi utilizado o `super()` para chamar a função `__init__` da classe pai, é possível utilizá-lo para chamar qualquer outra função. Isso permite, por exemplo, tratar os argumentos da função, aplicando modificações antes de chamar a função original, ou seu retorno, executando algum processamento em cima do retorno dela, não precisando rescrever toda a função. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/oo-de-outra-forma-4.md b/content/oo-de-outra-forma-4.md new file mode 100644 index 000000000..d458650f3 --- /dev/null +++ b/content/oo-de-outra-forma-4.md @@ -0,0 +1,161 @@ +Title: Orientação a objetos de outra forma: Herança múltiplas e mixins +Slug: oo-de-outra-forma-4 +Date: 2021-05-03 15:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +No [texto anterior](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-3dm7) foi apresentando o conceito de herança, que herda toda a estrutura e comportamento de uma classe, podendo estendê-la com outros atributos e comportamentos. Esse texto apresentará a ideia de [herança múltipla](https://pt.wikipedia.org/wiki/Heran%C3%A7a_m%C3%BAltipla), e uma forma para se aproveitar esse recurso, através de mixins. + +## Herança múltiplas + +Voltando ao sistema para lidar com dados das pessoas, onde algumas dessas pessoas possuem a possibilidade de acessar o sistema através de usuário e senha, também deseja-se permitir que outros sistemas autentiquem e tenham acesso os dados através de uma [API](https://pt.wikipedia.org/wiki/Interface_de_programa%C3%A7%C3%A3o_de_aplica%C3%A7%C3%B5es). Isso pode ser feito criando uma classe para representar os sistemas que terão permissão para acessar os dados. Exemplo: + +```python +class Sistema: + def __init__(self, usuario, senha): + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha +``` + +Porém, esse código repete a implementação feita para `PessoaAutenticavel`: + +```python +class PessoaAutenticavel(Pessoa): + def __init__(self, nome, sobrenome, idade, usuario, senha): + super().__init__(nome, sobrenome, idade) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha +``` + +Aproveitando que Python, diferente de outras linguagens, possui herança múltipla, é possível extrair essa lógica das classes, centralizando a implementação em uma outra classe e simplesmente herdá-la. Exemplo: + +```python +class Autenticavel: + def __init__(self, *args, usuario, senha, **kwargs): + super().__init__(*args, **kwargs) + self.usuario = usuario + self.senha = senha + + def autenticar(self, usuario, senha): + return self.usuario == usuario and self.senha == senha + + +class PessoaAutenticavel(Autenticavel, Pessoa): + ... + + +class Sistema(Autenticavel): + ... + + +p = PessoaAutenticavel(nome='João', sobrenome='da Silva', idade=20, + usuario='joao', senha='secreta') +``` + +A primeira coisa a ser observada são os argumentos `*args` e `**kwargs` no `__init__` da classe `Autenticavel`, eles são usados uma vez que não se sabe todos os argumentos que o `__init__` da classe que estenderá o `Autenticavel` espera receber, funcionando de forma dinâmica (mais sobre esse recurso pode ser visto na [documentação do Python](https://docs.python.org/pt-br/3/tutorial/controlflow.html#more-on-defining-functions)). + +A segunda coisa a ser verificada é que para a classe `PessoaAutenticavel`, agora cria em seus objetos, a estrutura tanto da classe `Pessoa`, quanto `Autenticavel`. Algo similar a versão sem orientação a objetos a baixo: + +```python +# Arquivo: pessoa_autenticavel.py + +import autenticavel +import pessoa + + +def init(p, nome, sobrenome, idade, usuario, senha): + pessoa.init(p, nome, sobrenome, idade) + autenticavel.init(p, usuario, senha) +``` + +Também vale observar que as classes `PessoaAutenticavel` e `Sistema` não precisam definir nenhuma função, uma vez que elas cumprem seus papéis apenas herdando outras classes, porém seria possível implementar funções específicas dessas classes, assim como sobrescrever as funções definidas por outras classes. + +## Ordem de resolução de métodos + +Embora herança múltiplas sejam interessantes, existe um problema, se ambas as classes pai possuírem uma função com um mesmo nome, a classe filha deveria chamar qual das funções? A do primeiro pai? A do último? Para lidar com esse problema o Python usa o MRO (*method resolution order*, ordem de resolução do método), que consiste em uma tupla com a ordem de qual classe o Python usará para encontrar o método a ser chamado. Exemplo: + +```python +print(PessoaAutenticavel.__mro__) +# (, , , ) +``` + +Por esse motivo que também foi possível chamar o `super().__init__` dentro de `Autenticavel`, que devido ao MRO, o Python chama o `__init__` da outra classe pai da classe que estendeu `Autenticavel`, em vez de precisar fazer um método `__init__` em `PessoaAutenticavel` chamando o `__init__` de todas as suas classes pais, como foi feito na versão sem orientação a objetos. E por isso a ordem `Autenticavel` e `Pessoa` na herança de `PessoaAutenticavel`, para fazer o MRO procurar os métodos primeiro em `Autenticavel` e depois em `Pessoa`. + +Para tentar fugir da complexidade que pode ser herança múltipla, é possível escrever classes que tem por objetivo unicamente incluir alguma funcionalidade em outra, como o caso da classe `Autenticavel`, que pode ser herdada por qualquer outra classe do sistema para permitir o acesso ao sistema. Essas classes recebem o nome de mixins, e adiciona uma funcionalidade bem definida. + +## Estendendo mixins + +Imagine se além de permitir o acesso ao sistema, também gostaríamos de registrar algumas tentativas de acesso, informando quando houve a tentativa e se o acesso foi concedido ou não. Como `Autenticavel` é uma classe, é possível extendê-la para implementar essa funcionalidade na função `autenticar`. Exemplo: + +```python +from datetime import datetime + + +class AutenticavelComRegistro(Autenticavel): + @staticmethod + def _get_data(): + return datetime.now().strftime('%d/%m/%Y %T') + + def autenticar(self, usuario, senha): + print(f'{self._get_data()} Tentativa de acesso de {usuario}') + acesso = super().autenticar(usuario, senha) + if acesso: + acesso_str = 'permitido' + else: + acesso_str = 'negado' + print(f'{self._get_data()} Acesso de {usuario} {acesso_str}') + return acesso + + +class PessoaAutenticavelComRegistro(AutenticavelComRegistro, Pessoa): + ... + + +class SistemaAutenticavelComRegistro(AutenticavelComRegistro, Sistema): + ... + + +p = PessoaAutenticavelComRegistro( + nome='João', sobrenome='da Silva', idade=20, + usuario='joao', senha='secreta', +) +p.autenticar('joao', 'secreta') +# Saída na tela: +# 23/04/2021 16:56:58 Tentativa de acesso de joao +# 23/04/2021 16:56:58 Acesso de joao permitido +``` + +Essa implementação utiliza-se do `super()` para acessar a função `autenticar` da classe `Autenticavel` para não precisar reimplementar a autenticação. Porém, antes de chamá-la, manipula seus argumentos para registrar quem tentou acessar o sistema, assim como também manipula o seu retorno para registrar se o acesso foi permitido ou não. + +Essa classe também permite analisar melhor a ordem em que as classes são consultadas quando uma função é chamada: + +```python +print(PessoaAutenticavelComRegistro.__mro__) +# (, , , , ) +``` + +Que também pode ser visto na forma de um digrama de classes: + +![Diagrama de classes](images/eduardoklosowski/oo-de-outra-forma-4/mro.png) + +Onde é feito uma [busca em profundidade](https://pt.wikipedia.org/wiki/Busca_em_profundidade), como se a função fosse chamada no primeiro pai, e só se ela não for encontrada, busca-se no segundo pai e assim por diante. Também é possível observar a classe `object`, que sempre será a última classe, e é a classe pai de todas as outras classes do Python quando elas não possuirem um pai declarado explicitamente. + +## Considerações + +Herança múltipla pode dificultar bastante o entendimento do código, principalmente para encontrar onde determinada função está definida, porém pode facilitar bastante o código. Um exemplo que usa bastante herança e mixins são as *views* baseadas em classe do django ([*class-based views*](https://docs.djangoproject.com/pt-br/3.2/topics/class-based-views/)), porém para facilitar a visualização existe o site [Classy Class-Based Views](https://ccbv.co.uk/) que lista todas as classes, e os mixins utilizados em cada uma, como pode ser visto em "Ancestors" como na [UpdateView](https://ccbv.co.uk/projects/Django/3.1/django.views.generic.edit/UpdateView/), que é usado para criar uma página com formulário para editar um registro já existente no banco, assim ela usa mixins para pegar um objeto do banco (`SingleObjectMixin`), processar formulário baseado em uma tabela do banco (`ModelFormMixin`) e algumas outras funcionalidades necessárias para implementar essa página. + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/oo-de-outra-forma-5.md b/content/oo-de-outra-forma-5.md new file mode 100644 index 000000000..d5741bfab --- /dev/null +++ b/content/oo-de-outra-forma-5.md @@ -0,0 +1,236 @@ +Title: Orientação a objetos de outra forma: ABC +Slug: oo-de-outra-forma-5 +Date: 2021-05-10 12:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Na discussão sobre [herança e mixins](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-multiplas-e-mixins-31eb) foram criadas várias classes, como `Autenticavel` e `AutenticavelComRegistro` que adicionam funcionalidades a outras classes e implementavam tudo o que precisavam para seu funcionamento. Entretanto podem existir casos em que não seja possível implementar todas as funções na própria classe, deixando com que as classes que a estende implemente essas funções. Uma forma de fazer isso é través das [ABC](https://docs.python.org/pt-br/3/library/abc.html) (*abstract base classes*, ou classes base abstratas). + +## Sem uso de classes base abstratas + +Um exemplo de classe que não é possível implementar todas as funcionalidades foi dada no texto [Encapsulamento da lógica do algoritmo](https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e), que discutia a leitura de valores do teclado até que um valor válido fosse lido (ou que repete a leitura caso um valor inválido tivesse sido informado). Nesse caso a classe `ValidaInput` implementava a lógica base de funcionamento, porém eram suas classes filhas (`ValidaNomeInput` e `ValidaNotaInput`) que implementavam as funções para tratar o que foi lido do teclado e verificar se é um valor válido ou não. + +```python +class ValidaInput: + mensagem_valor_invalido = 'Valor inválido!' + + def ler_entrada(self, prompt): + return input(prompt) + + def transformar_entrada(self, entrada): + raise NotImplementedError + + def validar_valor(self, valor): + raise NotImplementedError + + def __call__(self, prompt): + while True: + try: + valor = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_valor(valor): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return valor + + +class ValidaNomeInput(ValidaInput): + mensagem_valor_invalido = 'Nome inválido!' + + def transformar_entrada(self, entrada): + return entrada.strip().title() + + def validar_valor(self, valor): + return valor != '' + + +class ValidaNotaInput(ValidaInput): + mensagem_valor_invalido = 'Nota inválida!' + + def transformar_entrada(self, entrada): + return float(entrada) + + def validar_valor(self, valor): + return 0 <= valor <= 10 +``` + +Entretanto, esse código permite a criação de objetos da classe `ValidaInput` mesmo sem ter uma implementação das funções `transformar_entrada` e `validar_valor`. E a única mensagem de erro ocorreria ao tentar executar essas funções, o que poderia estar longe do problema real, que é a criação de um objeto a partir de uma classe que não prove todas as implementações das suas funções, o que seria semelhante a uma classe abstrata em outras linguagens. + +```python +obj = ValidaInput() + +# Diversas linhas de código + +obj('Entrada: ') # Exceção NotImplementedError lançada +``` + +## Com uso de classes base abstratas + +Seguindo a documentação da [ABC](https://docs.python.org/pt-br/3/library/abc.html), para utilizá-las é necessário informar a metaclasse `ABCMeta` na criação da classe, ou simplesmente estender a classe `ABC`, e decorar com `abstractmethod` as funções que as classes que a estenderem deverão implementar. Exemplo: + +```python +from abc import ABC, abstractmethod + + +class ValidaInput(ABC): + mensagem_valor_invalido = 'Valor inválido!' + + def ler_entrada(self, prompt): + return input(prompt) + + @abstractmethod + def transformar_entrada(self, entrada): + ... + + @abstractmethod + def validar_valor(self, valor): + ... + + def __call__(self, prompt): + while True: + try: + valor = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_valor(valor): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return valor +``` + +Desta forma, ocorrerá um erro já ao tentar criar um objeto do tipo `ValidaInput`, dizendo quais são as funções que precisam ser implementadas. Porém funcionará normalmente ao criar objetos a partir das classes `ValidaNomeInput` e `ValidaNotaInput` visto que elas implementam essas funções. + +```python +obj = ValidaInput() # Exceção TypeError lançada + +nome_input = ValidaNomeInput() # Objeto criado +nota_input = ValidaNotaInput() # Objeto criado +``` + +Como essas funções não utilizam a referência ao objeto (`self`), ainda é possível decorar as funções com `staticmethod`, como: + +```python +from abc import ABC, abstractmethod + + +class ValidaInput(ABC): + mensagem_valor_invalido = 'Valor inválido!' + + @staticmethod + def ler_entrada(prompt): + return input(prompt) + + @staticmethod + @abstractmethod + def transformar_entrada(entrada): + ... + + @staticmethod + @abstractmethod + def validar_valor(valor): + ... + + def __call__(self, prompt): + while True: + try: + valor = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_valor(valor): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return valor + + +class ValidaNomeInput(ValidaInput): + mensagem_valor_invalido = 'Nome inválido!' + + @staticmethod + def transformar_entrada(entrada): + return entrada.strip().title() + + @staticmethod + def validar_valor(valor): + return valor != '' + + +class ValidaNotaInput(ValidaInput): + mensagem_valor_invalido = 'Nota inválida!' + + @staticmethod + def transformar_entrada(entrada): + return float(entrada) + + @staticmethod + def validar_valor(valor): + return 0 <= valor <= 10 +``` + +Isso também seria válido para funções decoradas com `classmethod`, que receberiam a referência a classe (`cls`). + +## Considerações + +Não é necessário utilizar ABC para fazer o exemplo discutido, porém ao utilizar essa biblioteca ficou mais explícito quais as funções que precisavam ser implementados nas classes filhas, ainda mais que sem utilizar ABC a classe base poderia nem ter as funções, com: + +```python +class ValidaInput: + mensagem_valor_invalido = 'Valor inválido!' + + def ler_entrada(self, prompt): + return input(prompt) + + def __call__(self, prompt): + while True: + try: + valor = self.transformar_entrada(self.ler_entrada(prompt)) + if self.validar_valor(valor): + break + except ValueError: + ... + print(self.mensagem_valor_invalido) + return valor +``` + +Como Python possui [duck-typing](https://docs.python.org/pt-br/3/glossary.html#term-duck-typing), não é necessário uma grande preocupação com os tipos, como definir e utilizar interfaces presentes em outras implementações de orientação a objetos, porém devido à herança múltipla, ABC pode ser utilizada como interface que não existe em Python, fazendo com que as classes implementem determinadas funções. Para mais a respeito desse assunto, recomendo as duas lives do dunossauro sobre ABC ([1](https://www.youtube.com/watch?v=yLHV1__nZZw) e [2](https://www.youtube.com/watch?v=erAXvsuihPQ)), e a apresentação do Luciano Ramalho sobre [type hints](https://www.youtube.com/watch?v=AJK2LqrlnTE). + +Uma classe filha também não é obrigada a implementar todas as funções decoradas com `abstractmethod`, mas assim como a classe pai, não será possível criar objetos a partir dessa classe, apenas de uma classe filha dela que implemente as demais funções. Como se ao aplicar um `abstractmethod` tornasse a classe abstrata, e qualquer classe filha só deixasse de ser abstrata quando a última função decorada com `abstractmethod` for sobrescrita. Exemplo: + +```python +from abc import ABC, abstractmethod + + +class A(ABC): + @abstractmethod + def func1(self): + ... + + @abstractmethod + def func2(self): + ... + + +class B(A): + def func1(self): + print('1') + + +class C(B): + def func2(self): + print('2') + + +a = A() # Erro por não implementar func1 e func2 +b = B() # Erro por não implementar func2 +c = C() # Objeto criado +``` + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/oo-de-outra-forma-6.md b/content/oo-de-outra-forma-6.md new file mode 100644 index 000000000..19d708b4f --- /dev/null +++ b/content/oo-de-outra-forma-6.md @@ -0,0 +1,131 @@ +Title: Orientação a objetos de outra forma: Property +Slug: oo-de-outra-forma-6 +Date: 2021-05-17 18:00 +Category: Python +Tags: python, orientação a objetos +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Programador, formado em redes de computadores e estuda DevOps + +Seguindo com a série, chegou a hora de discutir sobre encapsulamento, ou seja, ocultar detalhes de implementação de uma classe do resto do código. Em algumas linguagens de programação isso é feito utilizando `protected` ou `private`, e às vezes o acesso aos atributos é feito através de funções *getters* e *setters*. Nesse texto vamos ver como o Python lida com essas questões. + +## Métodos protegidos e privados + +Diferente de linguagens como Java e PHP que possuem palavras-chave como `protected` e `private` para impedir que outras classes acessem determinados métodos ou atributos, Python deixa tudo como público. Porém isso não significa que todas as funções de uma classe podem ser chamadas por outras, ou todos os atributos podem ser lidos e alterados sem cuidados. + +Para que quem estiver escrevendo um código saiba quais as funções ou atributos que não deveriam ser acessados diretamente, segue-se o padrão de começá-los com `_`, de forma similar aos arquivos ocultos em sistemas UNIX, que começam com `.`. Esse padrão já foi seguido na classe `AutenticavelComRegistro` da postagem sobre [mixins](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-multiplas-e-mixins-31eb), onde a função que pega a data do sistema foi nomeada `_get_data`. Entretanto isso é apenas uma sugestão, nada impede dela ser chamada, como no exemplo a baixo: + +```python +from datetime import datetime + + +class Exemplo: + def _get_data(self): + return datetime.now().strftime('%d/%m/%Y %T') + + +obj = Exemplo() +print(obj._get_data()) +``` + +Porém algumas bibliotecas também utilizam o `_` para indicar outras informações como metadados do objeto, e que podem ser acessados sem muitos problemas. Assim é possível utilizar esse símbolo duas vezes (`__`) para indicar que realmente essa variável ou função não deveria ser acessada de fora da classe, apresentando erro de que o atributo não foi encontrado ao tentar executar a função, porém ela ainda pode ser acessada: + +```python +from datetime import datetime + + +class Exemplo: + def __get_data(self): + return datetime.now().strftime('%d/%m/%Y %T') + + +obj = Exemplo() +print(obj.__get_data()) # AttributeError +print(obj._Exemplo__get_data()) # Executa a função +``` + +## Property + +Os *getters* e *setters* muitas vezes são usados para impedir que determinadas variáveis sejam alteradas, ou validar o valor antes de atribuir a variável, ou ainda processar um valor a partir de outras variáveis. Porém como o Python incentiva o acesso direto as variáveis, existe a *property*, que ao tentar acessar uma variável ou alterar um valor, uma função é chamada. Exemplo: + +```python +class Pessoa: + def __init__(self, nome, sobrenome, idade): + self._nome = nome + self.sobrenome = sobrenome + self._idade = idade + + @property + def nome(self): + return self._nome + + @property + def nome_completo(self): + return f'{self.nome} {self.sobrenome}' + + @nome_completo.setter + def nome_completo(self, valor): + valor = valor.split(' ', 1) + self._nome = valor[0] + self.sobrenome = valor[1] + + @property + def idade(self): + return self._idade + + @idade.setter + def idade(self, valor): + if valor < 0: + raise ValueError + self._idade = valor + + def fazer_aniversario(self): + self.idade += 1 +``` + +Nesse código algumas variáveis são acessíveis através de *properties*, de forma geral, as variáveis foram definidas começando com `_` e com uma *property* de mesmo nome (sem o `_`). O primeiro caso é o `nome`, que possui apenas o *getter*, sendo possível o seu acesso como `obj.nome`, porém ao tentar atribuir um valor, será lançado um erro (`AttributeError: can't set attribute`). Em relação ao `sobrenome`, como não é necessário nenhum tratamento especial, não foi utilizado um *property*, porém futuramente pode ser facilmente substituído por um sem precisar alterar os demais códigos. Porém a função `nome_completo` foi substituída por um *property*, permitindo tanto o acesso ao nome completo da pessoa, como se fosse uma variável, quanto trocar `nome` e `sobrenome` ao atribuir um novo valor para essa *property*. Quanto a `idade` utiliza o *setter* do *property* para validar o valor recebido, retornando erro para idades inválidas (negativas). + +Vale observar também que todas as funções de *getter* não recebem nenhum argumento (além do `self`), enquanto as funções de *setter* recebem o valor atribuído à variável. + +Utilizando a [ABC](https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-abc-89b), ainda é possível informar que alguma classe filha deverá implementar alguma *property*. Exemplo: + +```python +from abc import ABC + + +class Pessoa(ABC): + def __init__(self, nome, sobrenome, idade): + self.nome = nome + self.sobrenome = sobrenome + self.idade = idade + + @property + @abstractmethod + def nome_completo(self): + ... + + +class Brasileiro(Pessoa): + @property + def nome_completo(self): + return f'{self.nome} {self.sobrenome}' + + +class Japones(Pessoa): + @property + def nome_completo(self): + return f'{self.sobrenome} {self.nome}' +``` + +## Considerações + +Diferente de algumas linguagens que ocultam as variáveis dos objetos, permitindo o seu acesso apenas através de funções, Python seguem no sentido contrário, acessando as funções de *getter* e *setter* como se fossem variáveis, isso permite começar com uma classe simples e ir adicionando funcionalidades conforme elas forem necessárias, sem precisar mudar o código das demais partes da aplicação, além de deixar transparente para quem desenvolve, não sendo necessário lembrar se precisa usar *getteres* e *setteres* ou não. + +De forma geral, programação orientada a objetos consiste em seguir determinados padrões de código, e as linguagens que implementam esse paradigma oferecem facilidades para escrever código seguindo esses padrões, e às vezes até ocultando detalhes complexos de suas implementações. Nesse contexto, eu recomendo a palestra do autor do [htop](https://htop.dev/) feita no FISL 16, onde ele comenta como usou [orientação a objetos em C](http://hemingway.softwarelivre.org/fisl16/high/41f/sala_41f-high-201507091200.ogv). E para quem ainda quiser se aprofundar no assunto de orientação a objetos no Python, recomendo os vídeos do [Eduardo Mendes](https://www.youtube.com/playlist?list=PLOQgLBuj2-3L_L6ahsBVA_SzuGtKre3OK) (também conhecido como dunossauro). + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/operadores_ternarios.md b/content/operadores_ternarios.md new file mode 100644 index 000000000..4b803b492 --- /dev/null +++ b/content/operadores_ternarios.md @@ -0,0 +1,79 @@ +Title: Trabalhando com operadores ternários +Date: 2018-10-06 09:21 +Tags: Python, tutorial, operadores ternários +Category: Geral +Slug: trabalhando-com-operadores-ternarios +Author: Vitor Hugo de Oliveira Vargas +About_author: Eterno estudante e fã da cultura DevOps +Email: vitor.hov@gmail.com +Github: otoru +Twitter: vitor_hov +Facebook: Ovitorugo +Linkedin: vitor-hov + +Quando estamos escrevendo um código qualquer, possivelmente +a expressão que mais utilizamos é o `if`. Para qualquer +tarefas que buscamos automatizar ou problemas que buscamos +resolver, sempre acabamos caindo em lógicas como "Se isso +acontecer, então faça aquilo, senão faça aquele outro...". + +Quando estamos falando de ações a serem executadas, pessoalmente +gosto da forma com que o código fica organizado em python quando +usamos este tipo de condições, por exemplo: + + :::python + + if vencer_o_thanos: + restaurar_a_paz() + + else: + foo() + +Graças a indentação e ao espaçamento, vemos onde onde começa e/ou +termina o bloco executado caso a varável `vencer_o_thanos` seja +`True`. Quanto mais `if`'s você aninhar, mais bonito seu +código fica e em momento algum o mesmo se torna mais confuso +(ao menos, não deveria se tornar). Entretanto, sempre fico +extremamente incomodado quando tenho de escrever um bloco apenas +marcar uma variável, como por exemplo: + + :::python + + if vencer_o_thanos: + paz = True + + else: + paz = False + +Por isso, para trabalhar com variáveis que possuem um valor condicional, +gosto sempre de trabalhar com expressões condicionais, ou como costumam +ser chamadas, **operadores ternários**. + +Operadores ternários são todos os operadores que podem receber três +operandos. Como as expressões condicionais costumam ser os operadores +ternários mais populares nas linguagens em que aparecem, acabamos por +associar estes nomes e considerar que são a mesma coisa. Cuidado ao tirar +este tipo de conclusão, mesmo que toda vogal esteja no alfabeto, o +alfabeto não é composto apenas por vogais. + +A estrutura de uma expressão condicional é algo bem simples, veja só: + + :::python + + paz = True if vencer_o_thanos else False + tipo_de_x = "Par" if x % 2 == 0 else "impar" + +Resumidamente, teremos **um valor seguido de uma condição e por fim seu +valor caso a condição seja falsa**. Pessoalmente acredito que apesar de um +pouco diferente, essa forma de escrita para casos como o exemplificado acima +é muito mais clara, mais explicita. + +Se você fizer uma tradução literal das booleanas utilizadas no primeiro exemplo, +lerá algo como `paz é verdadeira caso vencer_o_thanos, caso contrário é Falsa.` +já o segundo exemplo fica mais claro ainda, pois lemos algo como +`tipo_de_x é par caso o resto da divisão de x por 2 seja 0, se não, tipo_de_x é impar.`. + +Interpretar código dessa forma pode ser estranho para um programador. Interpretar +uma abertura de chave ou uma indentação já é algo mas natural. Todavia, para aqueles +que estão começando, o raciocínio ocorre de forma muito mais parecida com a descrita +acima. Espero que tenham gostado do texto e que esse conhecimento lhes seja útil. diff --git a/content/paralelismo_em_python_usando_concurrent_futures.md b/content/paralelismo_em_python_usando_concurrent_futures.md new file mode 100644 index 000000000..d502810f0 --- /dev/null +++ b/content/paralelismo_em_python_usando_concurrent_futures.md @@ -0,0 +1,167 @@ +Title: Paralelismo em Python usando concurrent.futures +Slug: paralelismo-em-python-usando-concurrent.futures +Date: 2016-02-19 09:00 +Tags: python, concorrencia, paralelismo +Author: José Cordeiro de Oliveira Junior +Email: cordjr@gmail.com +Github: cordjr +Site: http://letsgetincode.com.br +Linkedin: https://br.linkedin.com/in/josé-cordeiro-de-oliveira-junior-10a17b49 +Twitter: cordjr +Category: Python + + + Esse post tem por objetivo abordar o uso da bliblioteca [concurrent.futures](https://docs.python.org/dev/library/concurrent.futures.html) 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: + +```python +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%. + +```python +def increase_price_by_10_percent(price): + price += price / 10 * 100 + return price +``` + +Dando continuidade, definiremos mais três funções. + +```python +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; + 6. 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: + +```python + 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: + +```python +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`: + +```python + 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](https://github.com/cordjr/concurrent.futtures.sample/blob/master/main.py). + + + + + + + + + + + diff --git a/content/peewee-um-orm-python-minimalista.md b/content/peewee-um-orm-python-minimalista.md new file mode 100644 index 000000000..a0ffc5b3e --- /dev/null +++ b/content/peewee-um-orm-python-minimalista.md @@ -0,0 +1,337 @@ +Title: Peewee - Um ORM Python minimalista +Slug: peewee-um-orm-python-minimalista +Date: 2017-07-20 23:45:24 +Category: Python +Tags: Python, Peewee, ORM, banco de dados +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: michell.stuttgart +Site: https://mstuttgart.github.io/ +Summary: Conheça o Peewee, um prático e minimalista ORM Python + +[Peewee](http://peewee.readthedocs.io/en/latest/index.html) é um ORM destinado a criar e gerenciar tabelas de banco de dados relacionais através de objetos Python. Segundo a [wikipedia](https://pt.wikipedia.org/wiki/Mapeamento_objeto-relacional), um ORM é: + +> Mapeamento objeto-relacional (ou ORM, do inglês: Object-relational mapping) é uma técnica de desenvolvimento > utilizada para reduzir a impedância da programação orientada aos objetos utilizando bancos de dados relacionais. As tabelas do banco de dados são representadas através de classes e os registros de cada tabela são representados como instâncias das classes correspondentes. + +O que um ORM faz é, basicamente, transformar classes Python em tabelas no banco de dados, além de permitir construir *querys* usando diretamente objetos Python ao invés de SQL. + +O Peewee é destinado a projetos de pequeno/médio porte e se destaca pela simplicidade quando comparado a outros ORM mais conhecidos, como o SQLAlchemy. Uma analogia utilizada pelo autor da API e que acho muito interessante é que Peewee está para o SQLAlchemy assim como SQLite está para o PostgreSQL. + +Em relação aos recursos por ele oferecidos, podemos citar que ele possui suporte nativo a SQLite, PostgreSQL e MySQL, embora seja necessário a instalação de *drivers* para utilizá-lo com PostgreSQL e MySQL e suporta tanto Python 2.6+ quanto Python 3.4+. + +Neste tutorial, utilizaremos o SQLite, por sua simplicidade de uso e pelo Python possuir suporte nativo ao mesmo (usaremos o Python 3.5). + +## Instalação + +O Peewee pode ser facilmente instalado com o gerenciador de pacotes *pip* (recomendo a instalação em um virtualenv): + +``` +pip install peewee +``` + +## Criando o banco de dados + +Para criar o banco de dados é bem simples. Inicialmente passamos o nome do nosso banco de dados (a extensão `*.db` indica um arquivo do SQLite). + +```python +import peewee + +# Aqui criamos o banco de dados +db = peewee.SqliteDatabase('codigo_avulso.db') + +``` + +Diferente de outros bancos de dados que funcionam através um servidor, o SQLite cria um arquivo de extensão `*.db`, onde todos os nossos dados são armazenados. + +> Caso deseje ver as tabelas existentes no arquivo `codigo_avulso.db`, instale o aplicativo `SQLiteBrowser`. Com ele fica fácil monitorar as tabelas criadas e acompanhar o tutorial. +```shell + sudo apt-get install sqlitebrowser +``` + +A título de exemplo, vamos criar um banco destinado a armazenar nomes de livros e de seus respectivos autores. Iremos chamá-lo de `models.py`. + +Inicialmente, vamos criar a classe base para todos os nossos `models`. Esta é uma abordagem recomendada pela documentação e é considerada uma boa prática. Também adicionaremos um log para acompanharmos as mudanças que são feitas no banco: + +```python +# models.py + +import peewee + +# Criamos o banco de dados +db = peewee.SqliteDatabase('codigo_avulso.db') + + +class BaseModel(peewee.Model): + """Classe model base""" + + class Meta: + # Indica em qual banco de dados a tabela + # 'author' sera criada (obrigatorio). Neste caso, + # utilizamos o banco 'codigo_avulso.db' criado anteriormente + database = db + +``` + +A class `BaseModel` é responsável por criar a conexão com nosso banco de dados. + +Agora, vamos criar a model que representa os autores: + +```python +# models.py + +class Author(BaseModel): + + """ + Classe que representa a tabela Author + """ + # A tabela possui apenas o campo 'name', que receberá o nome do autor sera unico + name = peewee.CharField(unique=True) + +``` + +Se observamos a model `Author`, veremos que não foi especificado nenhuma coluna como *primary key* (chave primaria), sendo assim o Peewee irá criar um campo chamado `id` do tipo inteiro com auto incremento para funcionar como chave primária. +Em seguida, no mesmo arquivo `models.py` criamos a classe que representa os livros. Ela possui uma relação de "muitos para um" com a tabela de autores, ou seja, cada livro possui apenas um autor, mas um autor pode possuir vários livros. + +```python +# models.py + +class Book(BaseModel): + """ + Classe que representa a tabela Book + """ + + # A tabela possui apenas o campo 'title', que receberá o nome do livro + title = peewee.CharField(unique=True) + + # Chave estrangeira para a tabela Author + author = peewee.ForeignKeyField(Author) + +``` + +Agora, adicionamos o código que cria as tabelas `Author` e `Book`. + +```python +# models.py + +if __name__ == '__main__': + try: + Author.create_table() + print("Tabela 'Author' criada com sucesso!") + except peewee.OperationalError: + print("Tabela 'Author' ja existe!") + + try: + Book.create_table() + print("Tabela 'Book' criada com sucesso!") + except peewee.OperationalError: + print("Tabela 'Book' ja existe!") +``` +excerpt +Agora executamos o `models.py`: + +``` +python models.py +``` + +A estrutura do diretório ficou assim: + +```shell +. +├── codigo_avulso.db +├── models.py +``` + +Após executarmos o código, será criado um arquivo de nome `codigo_avulso.db` no mesmo diretório do nosso arquivo `models.py`, contendo as tabelas `Author` e `Book`. + +## Realizando o CRUD + +Agora vamos seguir com as 4 principais operações que podemos realizar em um banco de dados, também conhecida como CRUD. + +A sigla `CRUD` é comumente utilizada para designar as quatro operações básicas que pode-se executar em um banco de dados, sendo elas: + + - Create (criar um novo registro no banco) + - Read (ler/consultar um registro) + - Update (atualizar um registro) + - Delete (excluir um registro do banco) + +Iremos abordar cada uma dessas operações. + +### Create: Inserindo dados no banco + +Agora, vamos popular nosso banco com alguns autores e seus respectivos livros. Para isso criamos um arquivo `create.py`. A estrutura do diretório ficou assim: + +```shell +. +├── codigo_avulso.db +├── models.py +├── create.py +``` + +A criação dos registros no banco pode ser feito através do método `create`, quando desejamos inserir um registro apenas; ou pelo método `insert_many`, quando desejamos inserir vários registros de uma vez em uma mesma tabela. + +```python +# create.py + +from models import Author, Book + +# Inserimos um autor de nome "H. G. Wells" na tabela 'Author' +author_1 = Author.create(name='H. G. Wells') + +# Inserimos um autor de nome "Julio Verne" na tabela 'Author' +author_2 = Author.create(name='Julio Verne') + +book_1 = { + 'title': 'A Máquina do Tempo', + 'author_id': author_1, +} + +book_2 = { + 'title': 'Guerra dos Mundos', + 'author_id': author_1, +} + +book_3 = { + 'title': 'Volta ao Mundo em 80 Dias', + 'author_id': author_2, +} + +book_4 = { + 'title': 'Vinte Mil Leguas Submarinas', + 'author_id': author_1, +} + +books = [book_1, book_2, book_3, book_4] + +# Inserimos os quatro livros na tabela 'Book' +Book.insert_many(books).execute() + +``` + +### Read: Consultando dados no banco + +O Peewee possui comandos destinados a realizar consultas no banco. De maneira semelhante ao conhecido `SELECT`. Podemos fazer essa consulta de duas maneiras. Se desejamos o primeiro registro que corresponda a nossa pesquisa, podemos utilizar o método `get()`. + +```python +# read.py + +from models import Author, Book + +book = Book.get(Book.title == "Volta ao Mundo em 80 Dias").get() +print(book.title) + +# Resultado +# * Volta ao Munto em 80 Dias +``` + +Porém, se desejamos mais de um registro, utilizamos o método `select`. Por exemplo, para consultar todos os livros escritos pelo autor "H. G. Wells". + +```python +# read.py + +books = Book.select().join(Author).where(Author.name=='H. G. Wells') + +# Exibe a quantidade de registros que corresponde a nossa pesquisa +print(books.count()) + +for book in books: + print(book.title) + +# Resultado: +# * A Máquina do Tempo +# * Guerra dos Mundos +# * Vinte Mil Leguas Submarinas + +``` + +Também podemos utilizar outras comandos do SQL como `limit` e `group` (para mais detalhes, ver a documentação [aqui](http://peewee.readthedocs.io/en/latest/index.html)). + +A estrutura do diretório ficou assim: + +```sh +. +├── codigo_avulso.db +├── models.py +├── create.py +├── read.py +``` + +### Update: Alterando dados no banco + +Alterar dados também é bem simples. No exemplo anterior, se observarmos o resultado da consulta dos livros do autor "H. G. Wells", iremos nos deparar com o livro de título "Vinte Mil Léguas Submarinas". Se você, caro leitor, gosta de contos de ficção-científica, sabe que esta obra foi escrito por "Julio Verne", coincidentemente um dos autores que também estão cadastrados em nosso banco. Sendo assim, vamos corrigir o autor do respectivo livro. + +Primeiro vamos buscar o registro do autor e do livro: + +```python +# update.py + +from models import Author, Book + +new_author = Author.get(Author.name == 'Julio Verne') +book = Book.get(Book.title=="Vinte Mil Leguas Submarinas") +``` + +Agora vamos alterar o autor e gravar essa alteração no banco. + +```python +# update.py + +# Alteramos o autor do livro +book.author = new_author + +# Salvamos a alteração no banco +book.save() +``` + +A estrutura do diretório ficou assim: + +```sh +. +├── codigo_avulso.db +├── models.py +├── create.py +├── read.py +├── update.py +``` + +### Delete: Deletando dados do banco + +Assim como as operações anteriores, também podemos deletar registros do banco de maneira bem prática. Como exemplo, vamos deletar o livro "Guerra dos Mundos" do nosso banco de dados. + +```python +# delete.py + +from models import Author, Book + +# Buscamos o livro que desejamos excluir do banco +book = Book.get(Book.title=="Guerra dos Mundos") + +# Excluimos o livro do banco +book.delete_instance() + +``` + +Simples não? + +A estrutura do diretório ficou assim: + +```sh +. +├── codigo_avulso.db +├── models.py +├── create.py +├── read.py +├── update.py +├── delete.py +``` + +## Conclusão + +É isso pessoal. Este tutorial foi uma introdução bem enxuta sobre o Peewee. Ainda existem muitos tópicos que não abordei aqui, como a criação de *primary_key*, de campos *many2many* entre outros recursos, pois foge do escopo deste tutorial. Se você gostou do ORM, aconselho a dar uma olhada também na sua documentação, para conseguir extrair todo o potencial da ferramenta. A utilização de um ORM evita que o desenvolvedor perca tempo escrevendo *query* SQL e foque totalmente no desenvolvimento de código. + +## Referências + +* [Documentação do Peewee (em inglês)](http://peewee.readthedocs.io/en/latest/index.html) +* [An Intro to peewee – Another Python ORM](https://www.blog.pythonlibrary.org/2014/07/17/an-intro-to-peewee-another-python-orm/) +* [Introduction to peewee](http://jonathansoma.com/tutorials/webapps/intro-to-peewee/) +* [Introdução à Linguagem SQL](https://www.novatec.com.br/livros/introducao-sql/) \ No newline at end of file diff --git a/content/postgresql-django.rst b/content/postgresql-django.rst new file mode 100644 index 000000000..9a5a5eee1 --- /dev/null +++ b/content/postgresql-django.rst @@ -0,0 +1,214 @@ +PostgreSql e Django - parte 3 +============================= + +:date: 2015-02-19 14:10 +:tags: python, postresql, banco de dados +:category: Python, Banco de dados +:slug: postgresql-e-django +:author: Regis da Silva +:email: regis.santos.100@gmail.com +:github: rg3915 +:summary: Esta é a parte 3 (de 3) da série de posts sobre PostgreSql... + +Se você já leu o `Tutorial Postgresql - parte 1 `_ e `PostgreSql e Python3 - parte 2 `_ , este post é uma continuação. Aqui nós veremos como usar `PostgreSQL `_ no `Django `_. + +Para quem já leu `Two Scoops of Django `_ sabe que o `PyDanny `_ recomenda fortemente o uso do mesmo SGBD tanto em **produção** como em **testes**. Então esqueça `sqlite `_ para testes, use `MySql `_ ou `Oracle `_ ou `PostgreSQL `_ tanto em produção como em testes, e é sobre este último que vamos falar agora. + +.. figure:: /images/regisdasilva/postgresql_django.png + :alt: postgresql_django.png + +Então vejamos aqui como configurar o Postgresql para ser usado no `Django `_. + +Precisamos criar o banco manualmente +------------------------------------ + +Começando... + +.. code-block:: bash + + $ sudo su - postgres + +Veja o prompt: + +.. code-block:: bash + + postgres@myuser:~$ + +Criando o banco + +.. code-block:: bash + + $ createdb mydb + +Se existir o banco faça + +.. code-block:: bash + + $ dropdb mydb + +e crie novamente. Para sair digite + +.. code-block:: bash + + $ exit + +Django +------ + +Vamos criar um virtualenv e instalar o `psycopg2 `_, além do django. + +.. code-block:: bash + + virtualenv -p /usr/bin/python3 teste + cd teste + source bin/activate + pip install psycopg2 django + pip freeze + pip freeze > requirements.txt + +**Dica**: Para diminuir o caminho do prompt digite + +.. code-block:: bash + + $ PS1="(`basename \"$VIRTUAL_ENV\"`):/\W$ " + +**Dica**: + +.. code-block:: bash + + vim ~/.bashrc + + alias manage='python $VIRTUAL_ENV/manage.py' + +Com isto nós podemos usar apenas ``manage`` ao invés de ``python manage.py``. + +Criando o projeto +^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + django-admin.py startproject myproject . + cd myproject + python ../manage.py startapp core + +**Edite o settings.py** + +.. code-block:: python + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'mydb', + # 'NAME': os.path.join(BASE_DIR, 'mydb'), + 'USER': 'myuser', + 'PASSWORD': 'mypassword', + 'HOST': '127.0.0.1', + 'PORT': '', # 8000 is default + } + } + +**Rode a aplicação** + +.. code-block:: bash + + python manage.py migrate + python manage.py runserver + +http://127.0.0.1:8000/ ou http://localhost:8000/ + +**Edite o models.py** + +.. code-block:: python + + from django.db import models + from django.utils.translation import ugettext_lazy as _ + + + class Person(models.Model): + name = models.CharField(_('Nome'), max_length=50) + email = models.EmailField(_('e-mail'), max_length=30, unique=True) + age = models.IntegerField(_('Idade')) + active = models.BooleanField(_('Ativo'), default=True) + created_at = models.DateTimeField( + _('Criado em'), auto_now_add=True, auto_now=False) + + class Meta: + ordering = ['name'] + verbose_name = "pessoa" + verbose_name_plural = "pessoas" + + def __str__(self): + return self.name + +Leia mais em + +`Tutorial Django 1.7 `_ + +`Como criar um site com formulário e lista em 30 minutos? `_ + +**Edite o settings.py** novamente + +Em *INSTALLED_APPS* insira a app *core*. + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'myproject.core', + ) + +**Faça um migrate** + +.. code-block:: bash + + python manage.py makemigrations core + python manage.py migrate + python manage.py createsuperuser + +Um pouco de shell +^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + $ python manage.py shell + Python 3.4.0 (default, Apr 11 2014, 13:05:18) + [GCC 4.8.2] on linux + Type "help", "copyright", "credits" or "license" for more information. + (InteractiveConsole) + >>> + + + +Serve para manipular a app pelo **terminal**. + +.. code-block:: python + + >>> from myproject.core.models import Person + >>> p = Person.objects.create(name='Regis',email='regis@example.com',age=35) + >>> p.id + >>> p.name + >>> p.email + >>> p.age + >>> p.active + >>> p.created_at + >>> p = Person.objects.create(name='Xavier',email='xavier@example.com',age=66,active=False) + >>> persons = Person.objects.all().values() + >>> for person in persons: print(person) + >>> exit() + +Leia mais em + +`Tutorial Postgresql - parte 1 `_ + +`PostgreSql e Python3 - parte 2 `_ + +`Tutorial Django 1.7 `_ + +`Como criar um site com formulário e lista em 30 minutos? `_ + +`How To Install and Configure Django with Postgres, Nginx, and Gunicorn `_ + +http://www.postgresql.org/docs/9.4/static/tutorial-createdb.html + +http://www.postgresql.org/docs/9.4/static/index.html + +http://www.postgresql.org/docs/9.4/static/tutorial-sql.html diff --git a/content/postgresql-python3.rst b/content/postgresql-python3.rst new file mode 100644 index 000000000..c1585cae1 --- /dev/null +++ b/content/postgresql-python3.rst @@ -0,0 +1,126 @@ +PostgreSql e Python3 - parte 2 +============================== + +:date: 2015-02-17 13:00 +:tags: python, postresql, banco de dados +:category: Python, Banco de dados +:slug: postgresql-e-python3 +:author: Regis da Silva +:email: regis.santos.100@gmail.com +:github: rg3915 +:summary: Esta é a parte 2 (de 3) da série de posts sobre PostgreSql... + +Se você já leu o `Tutorial Postgresql - parte 1 `_ este post é uma continuação. Aqui nós veremos como manipular um banco de dados PostgreSql no Python3. + +.. figure:: /images/regisdasilva/postgresql_python.png + :alt: postgresql_python.png + +Além da instalação mostrada no primeiro post precisaremos de + +.. code-block:: bash + + $ sudo apt-get install python-psycopg2 # para python2 + # ou + $ sudo apt-get install python3-psycopg2 # para python3 + +Começando... + +.. code-block:: bash + + $ sudo su - postgres + +Veja o prompt: + +.. code-block:: bash + + postgres@myuser:~$ + +Criando o banco + +.. code-block:: bash + + $ createdb mydb + +Se existir o banco faça + +.. code-block:: bash + + $ dropdb mydb + +e crie novamente. Para sair digite + +.. code-block:: bash + + $ exit + +Abra o python3. + +.. code-block:: bash + + $ python3 + Python 3.4.0 (default, Apr 11 2014, 13:05:18) + [GCC 4.8.2] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> + +Importe o psycopg2 + +.. code-block:: python + + >>> import psycopg2 + +Conectando a um banco de dados existente + +.. code-block:: python + + >>> conn = psycopg2.connect("dbname=mydb user=myuser") + +Abrindo um cursor para manipular o banco + +.. code-block:: python + + >>> cur = conn.cursor() + +Criando uma nova tabela + +.. code-block:: python + + >>> cur.execute("CREATE TABLE person (id serial PRIMARY KEY, name text, age integer);") + +Inserindo dados.O Psycopg faz a conversão correta. Não mais injeção SQL. + +.. code-block:: python + + >>> cur.execute("INSERT INTO person (name, age) VALUES (%s, %s)",("O'Relly", 60)) + >>> cur.execute("INSERT INTO person (name, age) VALUES (%s, %s)",('Regis', 35)) + +Grava as alterações no banco + +.. code-block:: python + + >>> conn.commit() + +# Select + +.. code-block:: python + + >>> cur.execute("SELECT * FROM person;") + >>> cur.fetchall() + +Fecha a comunicação com o banco + +.. code-block:: python + + >>> cur.close() + >>> conn.close() + >>> exit() + +Leia também + +`Tutorial Postgresql - parte 1 `_ + +`PostgreSql e Django - parte 3 `_ + +http://initd.org/psycopg/docs/ + +http://initd.org/psycopg/docs/usage.html diff --git a/content/programacao-funcional-com-python-0.md b/content/programacao-funcional-com-python-0.md new file mode 100644 index 000000000..f6dfb4810 --- /dev/null +++ b/content/programacao-funcional-com-python-0.md @@ -0,0 +1,150 @@ +title: Programação funcional com Python #0 - Saindo da zona de conforto +Slug: progrmacao-funcional-com-python-0 +Date: 2017-11-20 19:43 +Tags: python,fp,funcional,functional programming +Author: Eduardo Mendes +Email: mendesxeduardo@gmail.com +Github: z4r4tu5tr4 +Site: http://youtube.com/c/eduardomendes +Linkedin: mendesxeduardo +Category: programação funcional + + +# 0. Saindo da zona de conforto + +Sinta-se um vencedor, se você chegou até aqui, isso significa que quer aprender mais sobre o mundo da programação. + +Aprender novos paradígmas podem te trazer muitas coisas positivas, assim como aprender linguagens diferentes, pois paradígmas e linguagens transpõem maneiras, estruturas e métodos de implementação completamente diferentes. Com isso você pode ter mais ferramentas para usar no dia a dia. Você pode aumentar sua capacidade de expressar ideias de diferentes maneiras. Eu penso que o maior limitador de um programador é a linguagem de programação em que ele tem domínio. Quando você aprende linguagens imperativas, como C, Python, Java e etc..., você se vê limitado ao escopo de criar e manipular variáveis. Não que isso seja uma coisa ruim, porém existem outras maneiras de resolver problemas e quando você tem conhecimento disso consegue avaliar melhor quando implementar cada tipo de coisa. + +Você pode me dizer que aprender diferentes tipos de estruturas e maneiras de computar é uma coisa negativa pois tudo é variável nesse contexto. Mas eu penso exatamente o contrário, quanto mais você aprender da sua língua nativa, no caso estamos falando em português, maior o campo de domínio que você tem sobre como se comunicar e expressar ideias. Assim como aprender outras línguas te darão mais fundamentos para expressar ideias em outros idiomas, que não são melhores que os seu, mas diferentes e compõem diferentes estruturas, e isso pode ser libertador. Não quero me prolongar nesse assunto, mas dizer que isso pode acrescentar muito na suas habilidades cognitivas, até mesmo para usar ferramentas que você já usa no seu dia a dia. + + +Vamos começar fazendo uma tentativa de entender os paradígmas de programação, sem muito falatório e complicações. Um exemplo muito legal é do David Mertz em "Functional Programming in Python": + +- Usa-se programação funcional quando se programa em Lisp, Haskell, Scala, Erlang, F# etc.. + +- Do mesmo modo que se usa programação imperativa quando se programada C/C++, Pascal, Java, Python etc... + +- Também quando se programa Prolog estamos programando usando o paradígma lógico. + + +Apesar de não ser uma definição muito elegante, talvez seja a melhor a ser dada em muitas ocasiões. Vamos tentar ser um pouco mais objetivos em relação ao estilo de computação, embora essa discussão não tenha fim: + +- O foco de usar programação imperativa está no ato de mudar variáveis. A computação se dá pela modificação dos estados das variáveis iniciais. Sendo assim, vamos pensar que tudo é definido no início e vai se modificando até que o resultado esperado seja obtido. + +- Na programação funcional, se tem a noção de que o estado deve ser substituído, no caso da avaliação, para criação de um novo 'objeto' que no caso são funções. + + +## 0.1 Mas de onde vem a programação funcional? + +O florescer da programação funcional nasce no Lisp (acrônomo para List Processing) para tentar resolver alguns problemas de inteligência artificial que eram provenientes da linguística, que tinha foco em processamento de linguagem natural que por sua vez eram focados em processamento de listas em geral. Isso justifica uma grande parte do conteúdo que vamos ver aqui e seus tipos de dados variam somente entre listas e átomos. E assim foi mantido o foco de processamento de listas em todas as linguagens funcionais e suas funções e abstrações para resolver problemas relativos a listas e estruturas iteráveis. Uma curiosidade é que para quem não sabe porque em lisp existem tantos parênteses é que ele é baseado em s-expression, uma coisa que temos um "equivalente" evoluído em python, que parte dos teoremas de gramáticas livres de contexto: + +```clojure +(+ 4 5) +``` + +Sim, isso é uma soma em lisp. Diferente das linguagens imperativas como costumamos ver: + +```c +4 + 5 +``` + +Uma assertiva pode ser feita dessa maneira: + +- Funcional (ou declarativa) +```clojure +(= 4 (+ 2 2)) +``` + +- Imperativa +```c +(2 + 2) == 4 +``` + +Chega de enrolação e vamos correr com essa introdução, não viemos aqui para aprender Lisp ou C. Mas acho que parte desse contexto pode nos ajudar e muito quando formos nos aprofundar em alguns tópicos. Pretendo sempre que iniciar uma nova ferramenta da programação funcional ao menos explicar em que contexto ela foi desenvolvida e para resolver cada tipo de problema. + +## 0.2 Técnicas usadas por linguagens funcionais + +Vamos tentar mapear o que as linguagens funcionais fazem de diferente das linguagens imperativas, mas não vamos nos aprofundar nesse tópicos agora, pois são coisas às vezes complexas sem o entendimento prévio de outros contextos, mas vamos tentar só explanar pra que você se sinta empolgado por estar aqui: + +- Funções como objetos de primeira classe: + - São funções que podem estar em qualquer lugar (em estruturas, declaradas em tempo de execução). + +- Funções de ordem superior: + - São funções que podem receber funções como argumentos e retornar funções. + +- Funções puras: + - São funções que não sofrem interferências de meios externos (variáveis de fora). Evita efeitos colaterais. + +- Recursão, como oposição aos loops: + - Frequentemente a recursão na matemática é uma coisa mais intuitiva e é só chamar tudo outra vez, no lugar de ficar voltando ao ponto inicial da iteração. + +- Foco em processamento de iteráveis: + - Como dito anteriormente, pensar em como as sequências podem nos ajudar a resolver problemas. + +- O que deve ser computado, não como computar: + - Não ser tão expressivo e aceitar que as intruções não tem necessidade de estar explicitas todas as vezes, isso ajuda em legibilidade. + +- Lazy evaluation: + - Criar sequências infinitas sem estourar nossa memória. + +## 0.3 Python é uma linguagem funcional? + +#### Não. Mas é uma linguagem que implementa muitos paradígmas e porque não usar todos de uma vez? + +O objetivo desse 'conjunto de vídeos' é escrever código que gere menos efeito colateral e código com menos estados. Só que isso tudo feito na medida do possível, pois Python não é uma linguagem funcional. Porém, podemos contar o máximo possível com as features presentes do paradígma em python. + +Exemplos de funcional (básicos) em python: + +```Python +# Gerar uma lista da string # Imperativo +string = 'Python' +lista = [] # estado inicial + +for l in string: + lista.append(l) # cada iteração gera um novo estado + +print(lista) # ['P', 'y', 't', 'h', 'o', 'n'] +``` + +```Python +# Gerar uma lista da string # Funcional +string = lambda x: x + +lista = list(map(str, string('Python'))) # atribuição a um novo objeto + +print(lista) # ['P', 'y', 't', 'h', 'o', 'n'] +``` + +Como você pode ver, depois de uma explanação básica das técnicas, a segunda implementação não sofre interferência do meio externo (Funções puras), evita loops e sua saída sem o construtor de list é lazy. Mas não se assuste, vamos abordar tudo isso com calma. + +## 0.4 A quem esse 'curso' é destinado? + +Primeiramente gostaria de dizer que roubei essa ideia dos infinitos livros da O’Reilly, que sempre exibem esse tópico. Mas vamos ao assunto. Este curso é para você que sabe o básico de Python, e quando digo básico quero dizer que consegue fazer qualquer coisa com um pouco de pesquisa na internet. O básico de programação se reduz a isso. Vamos falar sobre coisas simples e coisas mais complexas, mas pretendo manter o bom senso para que todos possam absorver o máximo de conteúdo possível. + +Então, caso você venha do Python (OO ou procedural) você vai encontrar aqui uma introdução a programação funcional descontraída e sem uma tonelada de material difícil de entender. Caso você venha de linguagens funcionais como Haskell e Lisp, você pode se sentir um pouco estranho com tantas declarações, mas aprenderá a se expressar em Python. Caso você venha de linguagens funcionais modernas como Clojure e Scala, as coisas são bem parecidas por aqui. + +Então tente tirar o máximo de proveito. Vamos nos divertir. + +## 0.5 Apresentando o Jaber + +Jaber é nosso aluno de mentira, mas vamos pensar que ele é um aluno que senta na primeira fileira e pergunta de tudo, sempre que acha necessário. Roubei essa ideia do livro de expressões regulares do Aurélio. Ele tem um personagem, Piazinho, e acho que toda interação com ele é sempre graciosa e tira dúvidas quando tudo parece impossível. + +## 0.6 Sobre as referências + +Não gosto muito de citar referências pois procurei não copiar texto dos livros, mas muita coisa contida neles serve de base para o entendimento de certos tópicos. Outro motivo é o nível de complexidade dos exemplos ou explicações que tentei reduzir ao máximo enquanto escrevia esses roteiros. Para um exemplo, você pode olhar o livro do Steven Lott, cheio de fórmulas e abstrações matemáticas que em certo ponto acabam comprometendo o entendimento de quem não tem uma sólida base em computação teórica ou matemática. + +Como um todo, as referências serviram como guia, foi o que lí quando dúvidas para explicações surgiram. Não tiro nenhum crédito delas e as exponho para que todos saibam que existem muitos livros bons e que boa parte do que é passado aqui, foi aprendido neles. + + +## 0.7 Mais sobre o histórico das linguagens funcionais + +Se você pretende realmente se aprofundar no assunto enquanto acompanha esse curso, fazer uma imersão ou coisa parecida. Tudo começa com o cálculo lambda mentalizado pelo incrível [Alonzo Church](https://en.wikipedia.org/wiki/Alonzo_Church). Caso você não o conheça, ele foi um matemático fantástico e teve uma carreira acadêmica brilhante. Foi o orientador de pessoas incríveis como Alan Turing, Raymond Smullyan etc... + +Outro grande homem e que vale a pena mencionar e ser buscado é o [Haskell Curry](https://pt.wikipedia.org/wiki/Haskell_Curry), um lógico que trouxe excelentes contribuições para o que chamamos hoje de programação funcional. + +A primeira linguagem funcional 'oficial' (não gosto muito de dizer isso) é o Lisp (List Processing) criada pelo fenomenal [John McCarthy](https://pt.wikipedia.org/wiki/John_McCarthy) que também vale a pena ser pesquisado e estudado. + +Veremos o básico sobre os tipos de função no próximo tópico. + +OBS: [Referências](https://github.com/z4r4tu5tr4/python-funcional/blob/master/referencias.md) usadas durante todos os tópicos. diff --git a/content/programacao-funcional-com-python-1.md b/content/programacao-funcional-com-python-1.md new file mode 100644 index 000000000..09b7bf490 --- /dev/null +++ b/content/programacao-funcional-com-python-1.md @@ -0,0 +1,269 @@ +title: Programação funcional com Python #1 - Funções +Slug: progrmacao-funcional-com-python-1 +Date: 2017-12-08 13:30 +Tags: python,fp,funcional,functional programming +Author: Eduardo Mendes +Email: mendesxeduardo@gmail.com +Github: z4r4tu5tr4 +Site: http://youtube.com/c/eduardomendes +Linkedin: mendesxeduardo +Category: programação funcional + +# 1. Funções + +Como nem tudo são flores, vamos começar do começo e entender algumas características das funções do python (o objeto função) e dar uma revisada básica em alguns conceitos de função só pra gente não se perder no básico depois. Então o primeiro tópico vai se limitar a falar da estrutura básica das funções em python, sem entrar profundamente em cada um dos tópicos. Será uma explanação de código e abrir a cabeça para novas oportunidades de código mais pythonicos e que preferencialmente gere menos efeito colateral. Mas calma, não vamos ensinar a fazer funções, você já está cheio disso. + +## 1.1 Funções como objeto de primeira classe + +Funções como objeto de primeira classe, são funções que se comportam como qualquer tipo nativo de uma determinada linguagem. Por exemplo: + +```python +# uma lista + +lista = [1, 'str', [1,2], (1,2), {1,2}, {1: 'um'}] +``` + +Todos esses exemplos são tipos de objetos de primeira classe em Python, mas no caso as funções também são. Como assim? Pode-se passar funções como parâmetro de uma outra função, podemos armazenar funções em variáveis, pode-se definir funções em estruturas de dados: + +```python +# Funções como objeto de primeira classe + +func = lambda x: x # a função anônima, lambda, foi armazenada em uma variável + +def func_2(x): + return x + 2 + +lista = [func, func_2] # a variável que armazena a função foi inserida em uma estrutura, assim como uma função gerada com def + +lista_2 = [lambda x: x, lambda x: x+1] # aqui as funções foram definidas dentro de uma estrutura +``` + +Como é possível notar, em python, as funções podem ser inseridas em qualquer contexto e também geradas em tempo de execução. Com isso nós podemos, além de inserir funções em estruturas, retornar funções, passar funções como parâmetro (HOFs), definir funções dentro de funções(closures) e assim por diante. Caso você tenha aprendido a programar usando uma linguagem em que as funções não são objetos de primeira classe, não se assuste. Isso faz parte da rotina comum do python. Preferencialmente, e quase obrigatoriamente, é melhor fazer funções simples, pequenas e de pouca complexidade para que elas não sofram interferência do meio externo, gerem menos manutenção e o melhor de tudo, possam ser combinadas em outras funções. Então vamos lá! + +## 1.2 Funções puras + +Funções puras são funções que não sofrem interferência do meio externo. Vamos começar pelo exemplo ruim: + +```python + +valor = 5 + +def mais_cinco(x): + return x + valor + +assert mais_cinco(5) == 10 # True + +valor = 7 + +assert mais_cinco(5) == 10 # AssertionError +``` + +`mais_cinco()` é o exemplo claro de uma função que gera efeito colateral. Uma função pura deve funcionar como uma caixa preta, todas as vezes em que o mesmo input for dado nela, ela terá que retornar o mesmo valor. Agora vamos usar o mesmo exemplo, só alterando a linha do return: + +```python + +valor = 5 + +def mais_cinco(x): + return x + 5 + +assert mais_cinco(5) == 10 # True + +valor = 7 + +assert mais_cinco(5) == 10 # True +``` + +Pode parecer trivial, mas muitas vezes por comodidade deixamos o meio influenciar no comportamento de uma função. Por definição o Python só faz possível, e vamos falar disso em outro tópico, a leitura de variáveis externas. Ou seja, dentro do contexto da função as variáveis externas não podem ser modificadas, mas isso não impede que o contexto externo a modifique. Se você for uma pessoa inteligente como o Jaber deve saber que nunca é uma boa ideia usar valores externos. Mas, caso seja necessário, você pode sobrescrever o valor de uma variável no contexto global usando a palavra reservada `global`. O que deve ficar com uma cara assim: + +```Python + +valor = 5 + +def teste(): + global valor # aqui é feita a definição + valor = 7 + +print(valor) # 7 +``` + +Só lembre-se de ser sempre coerente quando fizer isso, as consequências podem ser imprevisíveis. Nessa linha de funções puras e pequeninas, podemos caracterizar, embora isso não as defina, funções de ordem superior, que são funções que recebem uma função como argumento, ou as devolvem, e fazem a chamada das mesmas dentro do contexto da função que a recebeu como parâmetro. Isso resulta em uma composição de funções, o que agrega muito mais valor caso as funções não gerem efeitos colaterais. + + +## 1.3 Funções de ordem superior (HOFs) + +Funções de ordem superior são funções que recebem funções como argumento(s) e/ou retornam funções como resposta. Existem muitas funções embutidas em python de ordem superior, como: `map, filter, zip` e praticamente todo o módulo functools `import functools`. Porém, nada impede de criarmos novas funções de ordem superior. Um ponto a ser lembrado é que map e filter não tem mais a devida importância em python com a entrada das comprehensions (embora eu as adore), o que nos faz escolher única e exclusivamente por gosto, apesar de comprehensions serem mais legíveis (vamos falar disso em outro contexto), existem muitos casos onde elas ainda fazem sentido. Mas sem me estender muito, vamos ao código: + +```python + +func = lambda x: x+2 # uma função simples, soma mais 2 a qualquer inteiro + +def func_mais_2(funcao, valor): + """ + Executa a função passada por parâmetro e retorna esse valor somado com dois + + Ou seja, é uma composição de funções: + + Dado que func(valor) é processado por func_func: + func_mais_2(func(valor)) == f(g(x)) + """ + return funcao(valor) + 2 +``` + +Um ponto a tocar, e o que eu acho mais bonito, é que a função vai retornar diferentes respostas para o mesmo valor, variando a entrada da função. Nesse caso, dada a entrada de um inteiro ele será somado com 2 e depois com mais dois. Mas, vamos estender este exemplo: + +```python + +func = lambda x: x + 2 # uma função simples, soma mais 2 a qualquer inteiro + +def func_mais_2(funcao, valor): + """ + Função que usamos antes. + """ + return funcao(valor) + 2 + +assert func_mais_2(func, 2) == 6 # true + +def func_quadrada(val): + """ + Eleva o valor de entrada ao quadrado. + """ + return val * val + +assert func_mais_2(func_quadrada, 2) == 6 # true +``` + +### 1.3.1 Um exemplo usando funções embutidas: + +Muitas das funções embutidas em python são funções de ordem superior (HOFs) como a função map, que é uma das minhas preferidas. Uma função de map recebe uma função, que recebe um único argumento e devolve para nós uma nova lista com a função aplicada a cada elemento da lista: + +```python +def func(arg): + return arg + 2 + +lista = [2, 1, 0] + +list(map(func, lista)) == [4, 3, 2] # true + +``` + +Mas fique tranquilo, falaremos muito mais sobre isso. + +## 1.4 `__call__` + +Por que falar de classes? Lembre-se, Python é uma linguagem construída em classes, e todos os objetos que podem ser chamados/invocados implementam o método `__call__`: + +Em uma função anônima: + +```python +func = lambda x: x + +'__call__' in dir(func) # True +``` + +Em funções tradicionais: +```python +def func(x): + return x + +'__call__' in dir(func) # True +``` + +Isso quer dizer que podemos gerar classes que se comportam como funções? + +SIIIIIM. Chupa Haskell + +Essa é uma parte interessante da estrutura de criação do Python a qual veremos mais em outro momento sobre introspecção de funções, mas vale a pena dizer que classes, funções nomeadas, funções anônimas e funções geradoras usam uma base comum para funcionarem, essa é uma das coisas mais bonitas em python e que em certo ponto fere a ortogonalidade da linguagem, pois coisas iguais tem funcionamentos diferentes, mas facilita o aprendizado da linguagem, mas não é nosso foco agora. + +## 1.5 Funções geradoras + +Embora faremos um tópico extremamente focado em funções geradoras, não custa nada dar uma palinha, não? + +Funções geradoras são funções que nos retornam um iterável. Mas ele é lazy (só é computado quando invocado). Para exemplo de uso, muitos conceitos precisam ser esclarecidos antes de entendermos profundamente o que acontece com elas, mas digo logo: são funções lindas <3 + +Para que uma função seja geradora, em tese, só precisamos trocar o return por yield: + +```python + +def gen(lista): + for elemento in lista: + yield elemento + +gerador = gen([1, 2, 3, 4, 5]) + +next(gerador) # 1 +next(gerador) # 2 +next(gerador) # 3 +next(gerador) # 4 +next(gerador) # 5 +next(gerador) # StopIteration +``` + +Passando bem por cima, uma função geradora nos retorna um iterável que é preguiçoso. Ou seja, ele só vai efetuar a computação quando for chamado. + + +## 1.6 Funções anônimas (lambda) + +Funções anônimas, ou funções lambda, são funções que podem ser declaradas em qualquer contexto. Tá... Todo tipo de função em python pode ser declarada em tempo de execução. Porém funções anônimas podem ser atribuídas a variáveis, podem ser definidas dentro de sequências e declaradas em um argumento de função. Vamos olhar sua sintaxe: + +```python +lambda argumento: argumento +``` + +A palavra reservada `lambda` define a função, assim como uma `def`. Porém em uma `def` quase que instintivamente sempre quebramos linha: + +```Python +def func(): + pass +``` + +Uma das diferenças triviais em python é que as funções anônimas não tem nome. Tá, isso era meio óbvio, mas vamos averiguar: + +```Python +def func(): + pass + +func.__name__ # func + +lambda_func = lambda arg: arg + +lambda_func.__name__ # '' +``` + +O resultado `''` será o mesmo para qualquer função. Isso torna sua depuração praticamente impossível em python. Por isso os usuários de python (e nisso incluo todos os usuários, até aqueles que gostam de funcional) não encorajam o uso de funções lambda a todos os contextos da linguagem. Mas, em funções que aceitam outra funções isso é meio que uma tradição, caso a função (no caso a que executa o código a ser usado pelo lambda) não esteja definida e nem seja reaproveitada em outro contexto. Eu gosto de dizer que lambdas são muito funcionais em aplicações parciais de função. Porém, os lambdas não passam de açúcar sintático em Python, pois não há nada que uma função padrão (definida com `def`), não possa fazer de diferente. Até a introspecção retorna o mesmo resultado: + + +```Python +def func(): + pass + +type(func) # function + +lambda_func = lambda arg: arg + +type(lambda_func) # function +``` + +Uma coisa que vale ser lembrada é que funções anônimas em python só executam uma expressão. Ou seja, não podemos usar laços de repetição (`while`, `for`), tratamento de exceções (`try`, `except`, `finally`). Um simples `if` com uso de `elif` também não pode ser definido. Como sintaticamente só são aceitas expressões, o único uso de um `if` é o ternário: + + +```Python +valor_1 if condicao else valor_2 +``` + +O que dentro de um lambda teria essa aparência: + + +```Python +func = lambda argumento: argumento + 2 if argumento > 0 else argumento - 2 +``` + +Funções lambda também podem ter múltiplos argumentos, embora seu processamento só possa ocorrer em uma expressão: + +```Python +func = lambda arg_1, arg_2, arg_3: True if sum([arg_1, arg_2, arg_3]) > 7 else min([arg_1, arg_2, arg_3]) +``` + +Embora essa seja uma explanação inicial sobre as funções anônimas, grande parte dos tópicos fazem uso delas e vamos poder explorar melhor sua infinitude. + +Mas por hoje é só e no tópico seguinte vamos discutir, mesmo que superficialmente, iteradores e iteráveis e suas relações com a programação funcional. diff --git a/content/programacao-funcional-com-python-2.md b/content/programacao-funcional-com-python-2.md new file mode 100644 index 000000000..00bf49742 --- /dev/null +++ b/content/programacao-funcional-com-python-2.md @@ -0,0 +1,129 @@ +title: Programação funcional com Python #2 - Iteraveis e iteradores +Slug: progrmacao-funcional-com-python-2 +Date: 2017-12-23 22:50 +Tags: python,fp,funcional,functional programming +Author: Eduardo Mendes +Email: mendesxeduardo@gmail.com +Github: z4r4tu5tr4 +Site: http://youtube.com/c/eduardomendes +Linkedin: mendesxeduardo +Category: programação funcional + + +# 2. Iteráveis e iteradores + +O que são iteráveis? Basicamente e a grosso modo, iteráveis em python são todos os objetos que implementam o método `__getitem__` ou `__iter__`. Beleza, vamos partir do simples. + +Quase todos os tipos de dados em python são iteráveis, por exemplo: listas, strings, tuplas, dicionários, conjuntos, etc... + +Vamos aos exemplos, eles são sempre mágicos: + +```python +lista = [1, 2, 3, 4, 5] + +# iteração +for x in lista: + print(x) + +# 1 +# 2 +# 3 +# 4 +# 5 +``` + +Era só isso? Sim, nem doeu, fala a verdade. + +Em python, o comando `for` nos fornece um iterador implícito. O que? Não entendi. + +O laço `for` em python itera em cada elemento da sequência. Como no exemplo, o `for`, ou `foreach`, no caso vai passando por cada elemento da sequência. Não é necessária a implementação de um index como na linguagem C, onde a iteração é explícita: + +```C +for (i = 0; i > 10; i++){ + sequencia[i]; +} +``` + +## 2.1 `__getitem__` + +O padrão de projeto iterator em python já vem implementado por padrão, como já foi dito antes. Basta que um objeto tenha os métodos `__iter__` ou `__getitem__` para que um laço possa ser utilizado. + +Vamos exemplificar: + +```Python +class iteravel: + """ + Um objeto que implementa o `__getitem__` pode ser acessado por posição + """ + def __init__(self, sequencia): + self.seq = sequencia + + def __getitem__(self, posicao): + """ + Por exemplo, quando tentamos acessar um elemento da sequência usando + slice: + >>> iteravel[2] + + O interpretador python chama o __getitem__ do objeto e nos retorna + a posição solicitada + + um exemplo: + IN: lista = [1, 2, 3, 4] + IN: lista[0] + OUT: 1 + """ + return self.seq[posicao] +``` + +Então, pode-se compreender, sendo bem rústico, que todos os objetos que implementam `__getitem__` são iteráveis em python. + + +## 2.2 `__iter__` + +Agora os objetos que implementam `__iter__` tem algumas peculiaridades. Por exemplo, quando o iterável (vamos pensar no `for`) chamar a sequência, ela vai pedir o `__iter__` que vai retornar uma instância de si mesmo para o `for` e ele vai chamar o `__next__` até que a exceção `StopIteration` aconteça. + +Uma classe que implementa `__iter__`: + +```Python +class iteravel: + def __init__(self, sequencia): + self.data = sequencia + + def __next__(self): + """ + Neste caso, como o método pop do objeto data é chamado + (vamos pensar em uma lista) ele vai retornar o primeiro elemento + (o de index 0) e vai remove-lo da lista + + E quando a sequência estiver vazia ele vai nos retornar um StopIteration + O que vai fazer com que a iteração acabe. + + Porém, estamos iterando com pop, o que faz a nossa iteração ser a única, + pois ela não pode ser repetida, dado que os elementos foram + removidos da lista + """ + if not self.sequencia: + raise StopIteration + return self.sequencia.pop() + + def __iter__(self): + return self +``` + +Como é possível notar, o objeto com `__iter__` não necessita de um `__getitem__` e vise-versa. As diferenças partem do ponto em que um pode ser acessado por index/slice e outro não. Também um bom ponto é que nesse nosso caso, removemos os elementos da sequência, ou seja, ela se torna descartável. + +Esse conceito, de ser descartado, pode parecer um pouco estranho no início, mas economiza muita memória. E, como estamos falando de programação funcional, pode-se dizer que nossa sequência se torna imutável, pois não existe uma maneira de mudar os valores contidos na lista do objeto. + + +Seguem dois links maravilhosos explicando sobre iteração em python: + +- [PEP0243](https://www.python.org/dev/peps/pep-0234/) + +- [Iteração em Python: do básico ao genial](https://www.youtube.com/watch?v=ULj7ejvuzI8) + +O primeiro é a PEP sobre as estruturas dos iteráveis e o segundo um video do Guru Luciano Ramalho explicando tudo sobre iteradores. + + +Ah... Ia quase me esquecendo, se você não entendeu muita coisa sobre os dunders, você pode ler o [Python data model](https://docs.python.org/3/reference/datamodel.html#special-method-names). Obs: não me responsabilizo pelo programador melhor que você sairá desta página. + +Embora esse tópico seja talvez o mais curto, ele vai ser de fundamental importância para o entendimento de um pouco de tudo nesse 'Curso'. É sério. Vamos entender como trabalhar com iteráveis de uma maneira bonita no próximo tópico. diff --git a/content/publicando_seu_hello_world_no_heroku.md b/content/publicando_seu_hello_world_no_heroku.md new file mode 100644 index 000000000..155f79360 --- /dev/null +++ b/content/publicando_seu_hello_world_no_heroku.md @@ -0,0 +1,153 @@ +Title: Publicando seu Hello World no Heroku +Slug: publicando-seu-hello-world-no-heroku +Date: 2015-01-25 11:30 +Tags: web,tutorial,heroku,flask,git,deploy +Author: Diego Garcia +Email: drgarcia1986@gmail.com +Github: drgarcia1986 +Site: http://www.codeforcloud.info +Twitter: drgarcia1986 +Linkedin: drgarcia1986 +Category: heroku + + + +
+ +
+
+ +O que é uma aplicação web se ela não está efetivamente na web? Não precisa ser efetivamente uma aplicação de produção, as vezes precisamos validar protótipos, compartilhar ferramentas online ou até mesmo publicar uma aplicação pela satisfação de publicar. Para isso não é necessário se preocupar com infraestrutura ou gastar dinheiro em aluguel de servidores. Existem diversas opções gratuitas e simples para isso, uma delas é o **Heroku**. + + + +## Heroku + +O Heroku é uma das opção mais populares de [plataforma como serviço](http://pt.wikipedia.org/wiki/Plataforma_como_servi%C3%A7o) que suporta app escritos em diversas linguagens, dentre elas, **Python**. Nele existem [planos pagos e gratuitos](https://www.heroku.com/pricing) de acordo com o uso do serviço. +Para conhecer um pouco do processo de _deploy_ do heroku, criaremos um simples **Hello World** utilizando **Flask**. + +### O que você irá precisar? +#### Uma conta no Heroku +Não se preocupe, você pode criar uma conta gratuita e usufruir do serviço sem problemas. Entre no [_Sign up_](https://signup.heroku.com/) e crie uma conta informando seu nome e seu e-mail. + +#### Git +O Heroku utiliza o Git para realizar o deploy dos app. Você verá mais adianta que um simples `git push` é o suficiente para enviarmos nosso app para o heroku. + +#### Python, Pip, VirtualEnv +Bem, você vai criar sua aplicação em python não? Além da boa organização e isolamento para seu ambiente proporcionado pelo **virtualenv**, manter seu app em um virtualenv proporciona algumas praticidades. + +### Instalando o Toolbet +O **Toolbet** é uma poderosa ferramenta de linha de comando do heroku. É através dela que iremos criar nosso app no heroku. +No ubuntu (ou outras distribuições baseadas no debian) para instalar não poderia ser diferente, basta usar o todo poderoso `apt-get`. +```bash +user@machine:~/$ sudo apt-get install heroku +``` + +> Se você estiver usando outro sistema operacional, você pode baixar o instalador direto do [site oficial](https://toolbelt.heroku.com/). + +Após a instalação, faça o login no heroku através do toolbet para se certificar que tudo deu certo. +```bash +user@machine:~/$ heroku login +Enter your Heroku credentials. +Email: your@email.com +Password (typing will be hidden): +Authentication successful. +``` + +### Preparando o App +Iremos agora criar nosso app que será compartilhado com o mundo. Nesse processo não tera nada de anormal, apenas a criação de uma aplicação web como qualquer outra. +Criaremos um diretório chamado `heroku_hello_world`. +```bash +user@machine:~/$ mkdir heroku_hello_world +user@machine:~/$ cd heroku_hello_world +user@machine:~/heroku_hello_world$ +``` +Nele iremos criar um `virtualenv` e ativa-lo. +```bash +user@machine:~/heroku_hello_world$ virtualenv venv +New python executable in venv/bin/python +Installing setuptools, pip...done. +user@machine:~/heroku_hello_world$ source venv/bin/activate +(venv)user@machine:~/heroku_hello_world$ +``` +Como disse anteriormente, iremos fazer o deploy de um simples **Hello World**. A intenção aqui é ter um **how-to** de como fazer o deploy de um app simples no heroku. Sendo assim, o **Flask** é uma excelente opção para isso. Para instalar o Flask em nosso virtualenv, use o `pip`. +```bash +(venv)user@machine:~/heroku_hello_world$ pip install flask +``` +E finalmente vamos criar nosso _Hello World_ no arquivo `hello.py`. +```python +import os +from flask import Flask + + +app = Flask(__name__) + + +@app.route("/") +def index(): + return "

Hello World" + +if __name__ == "__main__": + port = int(os.environ.get("PORT", 5000)) + app.run(host='0.0.0.0', port=port) + +``` +Um detalhe importante fica por conta da variável `port`. No heroku não é possível subir o app na porta 5000 (porta default do Flask), mas o heroku seta a variável de ambiente `PORT` em seu ambiente, definindo em qual porta a aplicação deve rodar. Da maneira como está implementado nosso app, iremos conseguir rodar tanto no heroku como localmente (através da porta 5000). + +O heroku precisa conhecer as dependências de nosso app para que, no momento do deploy ele construa o ambiente de forma correta. Seguindo o padrão, o heroku procura pelas dependências através do arquivo `requirements.txt`. Como já isolamos nosso app em um virtualenv, basta utilizar o `pip freeze` para listar os packages instalados e direcionar a saida desse comando para o arquivo _requirements.txt_ +```bash +(venv)user@machine:~/heroku_hello_world$ pip freeze > requirements.txt +``` +### Preparando o Deploy +Iremos criar agora o arquivo `Procfile`, onde será escrito o comando que o heroku deverá usar para executar nosso app, basicamente o mesmo comando que utilizaríamos para rodar a aplicação localmente. Esse arquivo (assim como o _requirements.txt_) deverá estar na raiz da aplicação. + +```bash +(venv)user@machine:~/heroku_hello_world$ echo "web: python hello.py" > Procfile +``` +Agora que já possuímos todos os arquivos necessários, iremos iniciar o processo efetivo de deploy da aplicação. Como disse anteriormente, o heroku utiliza o git, sendo assim, nossa aplicação deverá estar em um repositório. Para isso, basta criar um repositório no diretório atual através do comando `git init`. +```bash +(venv)user@machine:~/heroku_hello_world$ git init +Initialized empty Git repository in /home/user/heroku_hello_world/.git/ +``` +Para não _sujar_ nosso repositório com arquivos desnecessários como por exemplo o virtualenv, crie um arquivo chamado `.gitignore` na raiz do repositório e nele iremos determinar quais arquivos o git deve ignorar. +``` +*.pyc +venv +``` +Vamos adicionar e commitar nossos arquivos nesse repositório através dos comandos `git add .` para adicionar todos os arquivos e `git commit` para criar nosso commit inicial. +```bash +(venv)user@machine:~/heroku_hello_world$ git add . +(venv)user@machine:~/heroku_hello_world$ git commit -m 'initial commit' +[master (root-commit) 33f63b5] initial commit + 4 files changed, 25 insertions(+) + create mode 100644 .gitignore + create mode 100644 Procfile + create mode 100644 hello.py + create mode 100644 requirements.txt +(venv)user@machine:~/heroku_hello_world$ +``` +Agora vamos criar nosso app no heroku através do commando `heroku apps:create [nome do app]`. O nome da aplicação deverá ser único, pois o heroku utiliza o nome da aplicação para compor a url. +```bash +(venv)user@machine:~/heroku_hello_world$ heroku apps:create dg-hello-world +Creating dg-hello-world... done, stack is cedar-14 +https://dg-hello-world.herokuapp.com/ | https://git.heroku.com/dg-hello-world.git +Git remote heroku added +``` +> É importante ter realizado o login no heroku através do toolbet antes. + +No resultado do comando `heroku apps:create` já são apresentadas duas das coisas mais importantes para nosso app, a url de acesso e repositório git onde deverá ser enviada nossa aplicação. + +> Se você executou esse comando no mesmo diretório onde criou seu repositório do git, o heroku já cria o apontamento para o repositório remoto com o nome de `heroku`, não sendo necessário utilizar o comando `git remote add`. + +Basicamente a url será no padrão `https://[nome do app].herokuapp.com/`. + +### Efetivando o Deploy +Finalmente iremos realizar o deploy de nossa aplicação. Todos os passos anteriores foram passos preparatórios, o que significa que basta executá-los uma vez. Daqui em diante, para fazer o deploy de nosso app, basta enviar os commits do repositório local, para o repositório do heroku, através do comando `git push heroku master`. +```bash +(venv)user@machine:~/heroku_hello_world$ git push heroku master +``` +E pronto, basta agora acessar a url do seu app (no caso desse exemplo foi [https://dg-hello-world.herokuapp.com/](https://dg-hello-world.herokuapp.com/)) e compartilhar com seus amigos =]. + +**Referências**
+[(Heroku) Getting Started With Python](https://devcenter.heroku.com/articles/getting-started-with-python)
+[Deployment on the Heroku Cloud](http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xviii-deployment-on-the-heroku-cloud) diff --git a/content/pyftpdlib_criando_um_servidor_ftp_simples_com_python.rst b/content/pyftpdlib_criando_um_servidor_ftp_simples_com_python.rst index 3ecc86ae0..d0c81b2ee 100644 --- a/content/pyftpdlib_criando_um_servidor_ftp_simples_com_python.rst +++ b/content/pyftpdlib_criando_um_servidor_ftp_simples_com_python.rst @@ -74,9 +74,9 @@ Cliente FTP Existem varios clientes para o protocolo FTP. -O Windows Explorer, gerenciador de arquivos padrão em ambiente Windows, possui suporte complete ao protocolo FTP. +O Windows Explorer, gerenciador de arquivos padrão em ambiente Windows, possui suporte completo ao protocolo FTP. -O navegador web Mozilla Firefox, bem como os navegadores Google Chrome, Internet Explorer, possem capacidade de acessar servidores ftp, mas somente em modo de leitura. +O navegador web Mozilla Firefox, bem como os navegadores Google Chrome, Internet Explorer, possuem capacidade de acessar servidores ftp, mas somente em modo de leitura. O complemento FireFTP para o navegador web Mozilla Firefox, adiciona ao Firefox suporte completo ao protocolo FTP. @@ -109,7 +109,7 @@ Instalando no Ubuntu 14.04 64bits Para instalar é relativamente simples. Você possui duas opções de como instalar. -**Opção 1** - Instalar diretametente à partir do repositorio do Ubuntu. : +**Opção 1** - Instalar diretametente à partir do repositório do Ubuntu. : Abra um terminal e execute: @@ -118,7 +118,7 @@ Abra um terminal e execute: sudo apt-get install python-pyftpdlib -Obs: O repositorio do ubuntu possui uma versão muito desatualizada (1.2) do pyftpdlib, que atualmente está na versão 1.4. +Obs: O repositório do ubuntu possui uma versão muito desatualizada (1.2) do pyftpdlib, que atualmente está na versão 1.4. Recomendo usar a opção 2. **Opção 2** - Instalar utilizando o *pip*: @@ -250,7 +250,7 @@ Por exemplo, poderiamos mudar a porta padrão Se você quiser iniciar o servidor FTP de modo que quem for acessar não necessite informar a porta, ou seja ele poderá acessar o servidor em um endereço similar a ``ftp://127.0.0.1`` ou ``ftp://endereço_ip_ou_hostname_atual_do_seu_servidor``, -é necessario executa-lo como super-usuário, informando a porta 21, que é a padrão do protocolo, conforme exemplificado abaixo. +é necessario executá-lo como super-usuário, informando a porta 21, que é a padrão do protocolo, conforme exemplificado abaixo. .. code-block:: bash diff --git a/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4.md b/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4.md new file mode 100644 index 000000000..7cf33ddf6 --- /dev/null +++ b/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4.md @@ -0,0 +1,165 @@ +Title: Python com Unittest, Travis CI, Coveralls e Landscape (Parte 3 de 4) +Slug: python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4 +Date: 2016-05-13 12:25:00 +Category: Python +Tags: git, travis-ci, python, coveralls, landscape, test, tutorial +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +Fala pessoal, tudo bem? + +Na [segunda](python-com-unittest-travis-ci-coveralls-e-landscape-parte-2-de-4.html) parte deste tutorial, aprendemos a usar o `Travis CI` para automatizar os testes do nosso projeto, facilitando a manutenção do código quando temos vários colaboradores. Nesta terceira parte, vamos configurar o serviço [Coveralls](https://coveralls.io) para que o mesmo gere relatórios de teste sobre o nosso projeto. Os relatórios são muito úteis quando desejamos verificar o quanto do nosso projeto está coberto por testes, evitando assim que alguma *feature* importante fique de fora. Assim como o `Travis CI`, o `Coveralls` será executado após cada *push* ou *pull request*. + +Diferente do tutorial anterior, serei breve sobre o processo de inscrição do `Coveralls`, focando mais no seu uso. + +#### Criando uma conta + +Antes de começarmos a usar o `Coveralls` precisamos criar uma conta no serviço. Isso pode ser feito [aqui](https://coveralls.io/). O serviço é totalmente gratuíto para projetos *opensource*. + +
+![](images/mstuttgart/snapshot_17.png) +
+ +Após a inscrição, você será levado para uma nova página com uma listagem dos repositórios que você possui no [Github](https://github.com/). + +
+![](images/mstuttgart/snapshot_18.png) +
+ +Na imagem acima já podemos visualizar o projeto que estou usando neste tutorial: *codigo-avulso-test-tutorial*. Caso o seu repositório não esteja na lista, clique no botão `ADD REPOS` no canto superior direito da tela. + +
+![](images/mstuttgart/snapshot_19.png) +
+ +Ao clicar no botão, você será redirecionado a uma página onde é possível slecionar quais repositórios serão analisados pelo `Coveralls`. Caso o repositório desejado não esteja na lista, clique no botão `RE-SYNC REPOS` no canto superior direito. Ele vai realizar o escaneamento do seu perfil no [Github](https://github.com/) e importar seus projetos. + +Clique no botão escrito `OFF` ao lado esquerdo do nome do repositório. Isso ativará o serviço para este repositório. + +
+![](images/mstuttgart/snapshot_20.png) +
+ +Clique no botão `DETAILS` ao lado direito do nome do repositório e você será redirecionado para uma tela de configuração. Aqui o passo mais interessante é pegar a *url* da `badge`para usarmos em nosso *README.md*. + +
+[![Coverage Status](https://coveralls.io/repos/github/mstuttgart/codigo-avulso-test-tutorial/badge.svg?branch=master)](https://coveralls.io/github/mstuttgart/codigo-avulso-test-tutorial?branch=master) +
+ +Na área superior da tela, temos o seguinte: + +
+![](images/mstuttgart/snapshot_21.png) +
+ +Clique em `EMBED` e uma janelá de dialogo irá se abrir, selecione e copie o código em `MARKDOWN`. + +
+![](images/mstuttgart/snapshot_22.png) +
+ +Agora cole o código no cabeçalho do seu arquivo *README.md*, semelhante ao que fizemos com o `Travis CI` no tutorial anterior. + +```markdown +# Codigo Avulso Test Tutorial +[![Build Status](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial.svg?branch=master)](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial) + +[![Coverage Status](https://coveralls.io/repos/github/mstuttgart/codigo-avulso-test-tutorial/badge.svg?branch=master)](https://coveralls.io/github/mstuttgart/codigo-avulso-test-tutorial?branch=master) + +``` +Concluída esta estapa, o próximo passo será adicionarmos o serviço em nosso projeto no `Github`. + +#### Adicionando o Coveralls + +Vamos adicionar o serviço durante o processo de teste do projeto. Assim, depois de cada *push* ou *pull request*, o `Coveralls` irá gerar o relatório sobre nossos testes. + +Abra o arquivo *.travis.yml* em seu editor. Teremos o seguinte código: + +```travis +language: python + +python: + - "2.7" + +sudo: required + +script: + - run setup.py test + +``` +Agora vamos alterá-lo adicionando a funcionalidade do `Coveralls`. O códio atualizado do *.travis.yml* pode ser visto a seguir: + +```travis +language: python + +python: + - "2.7" + +sudo: required + +install: + - pip install coveralls + +script: + - coverage run --source=codigo_avulso_test_tutorial setup.py test + +after_success: + - coveralls + +``` + +#### Tag "install": +Aqui adicionamos o comando + +```bash +pip install coveralls +``` + +A instalação do `coveralls` é necessaria para que possamos gerar os relatórios. Você pode instalá-lo em sua máquina e gerar relátorios em html. Fica a sugestão de estudo. + +#### Tag "script": +Aqui substimuímos o comando + +```bash +run setup.py test +``` +por + +```bash +coverage run --source=codigo_avulso_test_tutorial setup.py test +``` + +Esse comando executa os mesmo testes de antes, mas já prove um relatório sobre a cobertura de testes do seu código. + +#### Tag "after_success": +A última alteração foi adicionar a tag `after_success`. + +```bash +after_success: + - coveralls +``` + +Essa tag indica que após a execuação bem sucedida dos testes, deve-se iniciar o serviço de analise do `Coveralls`. + +Assim que terminar de fazer essas alterações você já pode enviar o seu código para o `Github`. Assim que subir o código, o `Travis CI` irá iniciar o processo de teste. +Finalizando os testes, o `Coveralls` será iniciado. Se tudo ocorrer bem, a badge que adicionamos no aquivo README.md do projeto será atualizada exibindo a porcentagem do nosso código +que está coberta por testes. Você pode clicar na badge ou ir até o seu perfil no site do [Coveralls](https://coveralls.io) e verificar com mais detalhes as informações sobre seu projeto. + +
+![](images/mstuttgart/snapshot_23.png) +
+ +Na seção `LATEST BUILDS` clique no último build disponível que será possível verificar a porcentagem cobertura de teste para cada arquivo do seu projeto. + +Caso tenha interessa, aqui está o link do repositorio que usei para esse tutorial: [codigo-avulso-test-tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial). + +#### Conclusão + +Aqui encerramos a terceira parte do nossa série de tutoriais sobre `Unittest`. O `Coveralls` ainda possui muitas configurações não mostradas aqui, então se você se interessar, fica a sugestão de estudo. No próximo tutorial veremos como utilizar o `Landscape`, um *linter* que analise nossos códigos atrás de problemas de sintaxe, formatação e possíveis erros de códigos (variáveis não declaradas, varíaveis com escopo incorreto e etc). + +É isso pessoal. Obrigado por ler até aqui e até o próximo tutorial! + +**Publicado originalmente:** [python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4](https://mstuttgart.github.io/2016/04/29/2016-04-29-python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4/) diff --git a/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4.md b/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4.md new file mode 100644 index 000000000..2ab7d0878 --- /dev/null +++ b/content/python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4.md @@ -0,0 +1,116 @@ +Title: Python com Unittest, Travis CI, Coveralls e Landscape (Parte 4 de 4) +Slug: python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4 +Date: 2016-05-20 21:09:18 +Category: Python +Tags: git, travis-ci, python, coveralls, landscape, test, tutorial +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +Fala pessoal, tudo bem? + +Na [terceira](python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4.html) parte deste tutorial, aprendemos a usar o `Coveralls` para gerar relatórios de testes para o nosso projeto. A próxima ferramenta que iremos estudar será o serviço `Landscape`. Neste tutorial serei breve, já que o uso *default* da ferramenta é bem simples. + +#### Sobre o Landscape + +[Landscape](https://landscape.io/) é uma ferramenta online semelhante ao já conhecido [PyLint](https://www.pylint.org/), ou seja, é um verificador de *bugs*, estilo e de qualidade de código para [Python](https://www.python.org/). + +
+![](images/mstuttgart/snapshot_32.png) +
+ +Quando ativamos a análise do `Landscape` em nosso repositório, ele é executado após cada *push* ou *pull request* e realiza uma varredura em nosso código fonte [Python](https://www.python.org/) atrás de possíveis *bugs*, como por exemplo variáveis sendo usadas antes de serem declaradas, nomes reservados sendo usados como nomes de variáveis e etc. Ele também verifica se a formatação do seu código esta seguindo a [PEP8](https://www.python.org/dev/peps/pep-0008/) e aponta possíveis falhas de *design* em seu código. + +Uma vez que a análise esteja finalizada, a ferramenta indica em porcentagem a "qualidade" do nosso código, ou em palavras mais precisas, o quanto nosso código está bem escrito segundo as boas práticas de desenvolvimento. Vale deixar claro que o `Landscape` não verifica se seu código funciona corretamente, isso é responsabilidade dos testes que você escreveu, como foi visto na [primeira parte](python-com-unittest-travis-ci-coveralls-e-landscape-parte-1-de-4.html) do tutorial. + +Semelhante as ferramentas dos tutoriais anteriores, o `Landscape` é totalmente gratuito para projetos *opensource*. + +#### Criando uma conta + +O processo de inscrição é simples. No topo da página temos a permissão de nos inscrevermos usando a conta do `Github`. Realize a inscrição e vamos as configurações. + +
+![](images/mstuttgart/snapshot_33.png) +
+ +#### Ativando o serviço + +De todas as ferramentas apresentadas, esta é a mais simples de configurar. O único passo necessário aqui é ativar o serviço para o nosso repositório. Como exemplo, estarei usando o mesmo repositório dos últimos tutoriais. Clique [aqui](https://github.com/mstuttgart/codigo-avulso-test-tutorial) para visualizar o repositório. + +Assim que realizar o cadastro, vamos nos deparar com uma tela contendo a listagem dos nosso repositórios que estão utilizando o serviço. Se você nunca usou o serviço provavelmente não terá nenhum repositório, então faça o seguinte: clique no botão `Sync with Github now`, para realizar a sincronização com a sua conta do [Github](https://github.com). Assim que a sincronização estiver completa, clique no botão `Add repository`. + +
+![](images/mstuttgart/snapshot_34.png) +
+ +Ao clicar, seremos levados a uma tela com a listagem de todos os repositórios que temos permissão de escrita. Procure o repositório que deseja ativar o serviço (lembrando que o `Landscape` funciona apenas para projetos `Python`) e o selecione (basta clicar sobre o nome do repositório). + +
+![](images/mstuttgart/snapshot_35.png) +
+ +Adicione o repositório clicando no botão verde `Add Repository`, logo abaixo da lista. Seremos novamente redirecionados a tela inicial, agora com o repositório escolhido já visível. + +
+![](images/mstuttgart/snapshot_36.png) +
+ + Inclusive, a partir desse momento, o `Coveralls` já irá iniciar a análise do seu projeto. Clique no nome do repositório para ver mais detalhes da analise. + +
+![](images/mstuttgart/snapshot_37.png) +
+ + No caso do meu projeto de teste, temos que a "saúde" do código está em `100%`, ou seja, nenhuma parte do código apresenta erros de estilo, *bugs* e está utilizando boas práticas de programação em todo seu escopo. + + Na barra lateral localizada à esquerda da página, temos alguns items, entre os quais os mais importantes são descritos a seguir: + + * `Error`: são instruções no código que provavelmente indicam um erro. Por exemplo, quando referenciamos uma variável sem declará-la antes ou realizamos a chamada de algum método inexistente. + * `Smells`: são sinais ou sintomas no código que possivelmente indicam uma falha no projeto do *software*. Diferentemente de um *bug*, *code smells* não indicam uso incorreto da linguagem de programação e nem impedem o *software* de funcionar. Ao invés disso, eles indicam falhas no *design* do projeto que podem atrasar seu desenvolvimento ou mesmo ser a porta de entrada para *bugs* no futuro. Exemplos de *code smells* são: métodos ou códigos duplicados, classes muito grandes, uso forçado de algum *design pattern* quando o mesmo poderia ser substituído por um código mais simples e fácil de manter, métodos muito longos ou com excessivo números de parâmetros e por aí vai. A lista pode crescer muito haha... para mais detalhes [leia](https://en.wikipedia.org/wiki/Code_smell). + * `Style`: como o nome sugere, este item exibe os erros de estilo em seu código indicando trechos de código que não estão seguindo as regras de estilo da `PEP8`, trechos de códigos com identação incorreta e etc. + +Como último passo, agora somente nos resta adicionar uma `badge` no arquivo `README.md` em nosso repositório. Assim poderemos ver a porcentagem de "saúde" do nosso projeto sem precisar acessar a página do `Landscape`. + +Na página com o resultado da análise (onde é exibido a porcentagem de "saúde" do seu projeto), podemos pegar a `badge` do `Landscape`. No canto superior direito da tela, você encontra os botões abaixo: + +
+![](images/mstuttgart/snapshot_38.png) +
+ +Clique na `badge` (onde está escrito *health*) e a seguinte janela será exibida: + +
+![](images/mstuttgart/snapshot_39.png) +
+ +Selecione o texto da opção `Markdown` e cole-o no `README.md` do seu repositório. O meu `README.md` ficou assim: + +```markdown +# Codigo Avulso Test Tutorial +[![Build Status](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial.svg?branch=master)](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial) + +[![Coverage Status](https://coveralls.io/repos/github/mstuttgart/codigo-avulso-test-tutorial/badge.svg?branch=master)](https://coveralls.io/github/mstuttgart/codigo-avulso-test-tutorial?branch=master) + +[![Code Health](https://landscape.io/github/mstuttgart/codigo-avulso-test-tutorial/master/landscape.svg?style=flat)](https://landscape.io/github/mstuttgart/codigo-avulso-test-tutorial/master) + +``` + +Também é possível configurar o `Landscape` para que o mesmo exclua algum diretório/arquivo da análise (muito útil com arquivos de interface compilados, usando por quem trabalha com PyQt/PySide) entre outras opções, mas isso fica para um tutorial futuro. + +Abaixo podemos ver as três `badges` que adicionamos em nosso projeto. Clique [aqui](https://github.com/mstuttgart/codigo-avulso-test-tutorial) para acessar o repositório. + +
+![](images/mstuttgart/snapshot_40.png) +
+ +#### Conclusão + +Pronto pessoal, agora temos o nosso repositório exibindo informações sobre os testes unitários, relatórios de testes e analises de qualidade de código. Isso não garante que seu projeto seja livre de falhas e *bugs*, mas te ajuda a evitá-los. + +Vale lembrar que todas essas ferramentas ajudam muito, mas nada substitui o senso crítico e o hábito de sempre usar boas práticas durante o desenvolvimento. Por isso sempre busque aprender mais, estudar mais, ser humilde e ouvir quem tem mais experiência que você. Enfim, ser um programador e uma pessoa melhor a cada dia. Fica o conselho para todos nós, incluindo para este que vos escreve. + +Espero que tenham gostado desta série de tutoriais. Obrigado por ler até aqui e até o próximo *post*. + +**Publicado originalmente:** [python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4](https://mstuttgart.github.io/2016/05/07/2016-05-07-python-com-unittest-travis-ci-coveralls-e-landscape-parte-4-de-4/) diff --git a/content/python-com-unittest-travis-e-coveralls-parte-1-de-4.md b/content/python-com-unittest-travis-e-coveralls-parte-1-de-4.md new file mode 100644 index 000000000..8f026e6ab --- /dev/null +++ b/content/python-com-unittest-travis-e-coveralls-parte-1-de-4.md @@ -0,0 +1,392 @@ +Title: Python com Unittest, Travis CI, Coveralls e Landscape (Parte 1 de 4) +Slug: python-com-unittest-travis-ci-coveralls-e-landscape-parte-1-de-4 +Date: 2016-05-06 01:42:18 -0300 +Category: Python +Tags: git, travis-ci, python, coveralls, landscape, test, tutorial +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +Durante o desenvolvimento de um software, tão importante quanto escrever um código organizado e que siga as melhores práticas, é garantir que o mesmo cumpra os requisitos a que ele se propõe. Em outras palavras, garantir que o software funcione de maneira adequada. + +O processo de testes de um software faz parte do seu desenvolvimento, porém muitas vezes ele é encarado como algo tedioso e desnecessário. Entretanto, todo bom desenvolvedor sabe que investir tempo escrevendo testes para seu software está longe de ser "desperdício de tempo". O processo de teste, quando feito por uma pessoa, além de sujeitos a falhas é tedioso e demorado. Tendo isso em mente, podemos lançar mão de ferramentas que realizarão o processo de teste por nós. Em Python, umas das ferramentes da bibloteca padrão destinada a teste é a `Unittest`, que usaremos nesse tutorial. + +Nesta série de postagem, irei mostrar o passo-a-passo na criação de testes unitários para um pequeno projeto que vamos criar no github. Vou explicar como configurar a ferramenta [Travis](https://travis-ci.org/), que será responsável por executar os nossos testes no github. A ferramenta [Coveralls](https://coveralls.io/), que mapeia nosso código, e nos indica o quanto dele está coberto por testes. E como bônus, adicionar ao nosso projeto o [Landscape](https://landscape.io), ferramenta que monitora a "saúde" do nosso código. + +### Iniciando nosso projeto + +Inicialmente, criei no [github](https://github.com/) um repositório que vai receber meu código e que posteriormente será configurado para rodar nossos testes. No meu caso, o repositório foi esse [codigo-avulso-test-tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial). Após realizar o clone, criei a seguite estrutura de diretorios: + +``` +. +├── codigo_avulso_test_tutorial +│   └── __init__.py +├── LICENSE +├── README.md +└── test + └── __init__.py + +``` + +O diretório `codigo_avulso_test_tutorial` receberá o código da nossa aplicação e o diretório `test` receberá o código de teste. +O nosso projeto consiste de um grupo de classes representando figuras geométricas (quadrados, círculos e etc). Teremos uma classe base chamada `FiguraGeometrica` que possui dois métodos, a saber: `get_area` e `get_perimetro`, sendo ambos metódos abstratos. Cada uma dessas classes filhas de `FiguraGeometrica` irá possuir sua própria implementação desses métodos. + +Dentro do diretório `codigo_avulso_test_tutorial`, irei criar os fontes do nosso código: + +```bash +touch figura_geometrica.py circulo.py quadrado.py +``` + +Dentro do diretório `test`, irei criar os fontes do nosso código de teste: + +```bash +touch figura_geometrica_test.py circulo_test.py quadrado_test.py +``` + +Uma observação importante é que os arquivos de teste devem ter o nome terminado em `test`, para que o módulo de Unittest encontre os nossos arquivos de teste automaticamente. Após a criação dos arquivos, teremos a seguinte estrutura de diretório: + +``` +. +├── codigo_avulso_test_tutorial +│   ├── circulo.py +│   ├── figura_geometrica.py +│   ├── __init__.py +│   └── quadrado.py +├── LICENSE +├── README.md +└── test + ├── circulo_test.py + ├── figura_geometrica_test.py + ├── __init__.py + └── quadrado_test.py +``` + +Iniciemos agora a implementação do nosso projeto. Mas antes vamos dar uma olhada em alguns conceitos. + +### Test Driven Development (TDD) + +Neste momento, leitor, você deve estar se perguntando: "Não deveríamos escrever primeiro o nosso código e depois escrever os testes?". + +Não necessariamente. O processo de escrever os testes antes do código é chamado de `TDD - Test Driven Development`. Segundo a [wikipedia](https://pt.wikipedia.org/wiki/Test_Driven_Development): + +> "Test Driven Development (TDD) ou em português Desenvolvimento guiado por testes é uma técnica de desenvolvimento de software que baseia em um ciclo curto de repetições: Primeiramente o desenvolvedor escreve um caso de teste automatizado que define uma melhoria desejada ou uma nova funcionalidade. Então, é produzido código que possa ser validado pelo teste para posteriormente o código ser refatorado para um código sob padrões aceitáveis. Kent Beck, considerado o criador ou o 'descobridor' da técnica, declarou em 2003 que TDD encoraja designs de código simples e inspira confiança[1] . Desenvolvimento dirigido por testes é relacionado a conceitos de programação de Extreme Programming, iniciado em 1999,[2] mas recentemente tem-se criado maior interesse pela mesma em função de seus próprios ideais.[3] Através de TDD, programadores podem aplicar o conceito de melhorar e depurar código legado desenvolvido a partir de técnicas antigas.[4]" + +### Criando o setup.py + +Antes de começar a implementar o códigos de teste, vamos criar o arquivo `setup.py`. Esse arquivo contém informações sobr e o nosso módulo python e facilita em muito a utilização dos testes. Então, vamos criar o arquivo `setup.py` na pasta raiz do nosso projeto. + +```bash +touch setup.py +``` + +A estrutura do nosso projeto agora está assim: + +``` +. +├── codigo_avulso_test_tutorial +│   ├── circulo.py +│   ├── figura_geometrica.py +│   ├── __init__.py +│   └── quadrado.py +├── LICENSE +├── README.md +├── setup.py +└── test + ├── circulo_test.py + ├── figura_geometrica_test.py + ├── __init__.py + └── quadrado_test.py +``` + +Abra o `setup.py` em um editor e adicione as informações conforme exemplo abaixo: + +```python +# -*- coding: utf-8 -*- +from setuptools import setup +setup( + name='codigo-avulso-test-tutorial', + packages=['codigo_avulso_test_tutorial', 'test'], + test_suite='test', +) +``` + +No código acima, `name` representa o nome do seu projeto, `packages` são os diretórios do seu projeto que possuem código fonte e `test_suite` indica o diretório onde estão os fontes de teste. É importante declarar esse diretório pois o Unittest irá procurar dentro dele os arquivos de teste que iremos escrever. + +### Criando testes para a classe FiguraGeometrica +Agora, vamos usar a lógica do TDD. Primeiro criamos o código de teste de uma classe para em seguida criamos o código da mesma. Das classes que criamos, o arquivo `figura_geometrica.py` servirá como uma classe base para as outras classes. Então vamos começar por elá. + +Abra o arquivo `figura_geometrica_test.py` e seu editor preferido e adicione o código abaixo: + +```python + +# -*- coding: utf-8 -*- +from unittest import TestCase +from codigo_avulso_test_tutorial.figura_geometrica import FiguraGeometrica + +# O nome da classe deve iniciar com a palavra Test +class TestFiguraGeometrico(TestCase): + + # Serve para incializar variavei que usaremos + # globalmente nos testes + def setUp(self): + TestCase.setUp(self) + self.fig = FiguraGeometrica() + + # Retorna uma NotImplementedError + # O nome do metodo deve comecar com test + def test_get_area(self): + self.assertRaises(NotImplementedError, self.fig.get_area) + + # Retorna uma NotImplementedError + # O nome do metodo deve comecar com test + def test_get_perimetro(self): + self.assertRaises(NotImplementedError, self.fig.get_perimetro) + +``` +Como podemos observar no código acima, a seguinte linha: + +```python +def test_get_area(self): + self.assertRaise(self.fig.test_get_area(), NotImplementedError) +``` + +Realiza o seguinte teste. Com o objeto `self.fig` criado no método `setUp()`, tentamos chamar o método `test_get_perimetro` da classe `FiguraGeometrica`, porém ele verifica se ocorreu a exceção `NotImplementedError`. Isso é feito porque a classe `FiguraGeometrica` é uma classe abstrata e possui ambos os métodos `get_area` e `get_perimetro` vazios. Isso irá ficar mais claro quando adicionarmos o código da classe `FiguraGeometrica`. Então, abra o arquivo `figura_geometrica.py` em seu editor e vamos adicionar o seguinte código: + +```python +# -*- coding: utf-8 -*- + +class FiguraGeometrica(object): + + # Retorna a area da figura + def get_area(self): + raise NotImplementedError + + # Retorna o perimetro da figura + def get_perimetro(self): + raise NotImplementedError + +``` + +A class acima é bem simples. Ela possui um método que retorna a área e outro que retorna o perímetro da figura. Ambos são métodos *abstratos*, ou seja, devem ser implementados nas classes filhas da classe `FiguraGeometrica`. Se criarmos um objeto dessa classe e chamarmos um dos dois métodos, uma exceção do tipo `NotImplementedError` será lançada, pois ambos os métodos possuem escopo vazio. + +Finalmente podemos executar o teste da nossa classe. Usando o terminal, no diretorio em que o arquivo `setup.py` está, execute o seguinte comando: + +```bash +python setup.py test +``` + +Esse nosso comando vai executar a nossa classe `TestFiguraGeometrica`. Se tudo estiver correto, teremos a seguinte saída: + +``` +running test +running egg_info +writing codigo_avulso_test_tutorial.egg-info/PKG-INFO +writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt +writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt +reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +running build_ext +test_get_area (test.figura_geometrica_test.TestFiguraGeometrico) ... ok +test_get_perimetro (test.figura_geometrica_test.TestFiguraGeometrico) ... ok + +---------------------------------------------------------------------- +Ran 2 tests in 0.000s + +OK +``` + +Caso apareça uma resposta diferente, dê uma olhada na própria saída do teste. Ele indica onde está o erro. Provavelmente, pode ter sido algum erro de digitação, pois os exemplos deste tutorial foram todos testados. + +### Criando testes para a classe Quadrado + +Vamos criar agora outras classes que realmente fazem algo de útil e seus respectivos testes. Começando pela classe Quadrado, vamos escrever um teste para a mesma no arquivo `quadrado_test.py`. + +```python +# -*- coding: utf-8 -*- + +from unittest import TestCase +from codigo_avulso_test_tutorial.quadrado import Quadrado + +class TestQuadrado(TestCase): + + def setUp(self): + TestCase.setUp(self) + self.fig = Quadrado() + + def test_get_area(self): + # Verificamos se o resultado é o esperado + # de acordo com a formula de area do quadrado + self.fig.lado = 2 + self.assertEqual(self.fig.get_area(), 4) + self.fig.lado = 7.0 + self.assertEqual(self.fig.get_area(), 49.0) + + def test_get_perimetro(self): + self.fig.lado = 2 + self.assertEqual(self.fig.get_perimetro(), 8) + self.fig.lado = 7.0 + self.assertEqual(self.fig.get_perimetro(), 28.0) + +``` + +Em seguida, adicionamos o código da classe `Quadrado` no arquivo `quadrado.py`: + +```python +# -*- coding: utf-8 -*- + +from figura_geometrica import FiguraGeometrica + +class Quadrado(FiguraGeometrica): + + def __init__(self): + self.lado = 0 + + # Retorna a area do quadrado + def get_area(self): + return self.lado**2 + + # Retorna o perimetro do quadrado + def get_perimetro(self): + return 4 * self.lado + +``` + +Assim como fizemos no exemplo anterior, executamos os testes: + +```bash +python setup.py test +``` + +Se tudo estiver certo, teremos a seguinte saída. + +``` +running test +running egg_info +writing codigo_avulso_test_tutorial.egg-info/PKG-INFO +writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt +writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt +reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +running build_ext +test_get_area (test.quadrado_test.TestQuadrado) ... ok +test_get_perimetro (test.quadrado_test.TestQuadrado) ... ok +test_get_area (test.figura_geometrica_test.TestFiguraGeometrico) ... ok +test_get_perimetro (test.figura_geometrica_test.TestFiguraGeometrico) ... ok + +---------------------------------------------------------------------- +Ran 4 tests in 0.000s + +OK +``` + +Uma detalhe interessante a ser observado é que agora os testes da classe `Quadrado` estão sendo executados junto com os testes da classe `FiguraGeometrica` sem que fosse necessário alterar nenhuma configuração do projeto, ou adicionar algum novo diretório no arquivo `setup.py`. Isso acontece por que usamos a sufixo `_test` no nome dos nossos código fonte de teste e também adicionamos o diretório `test` na tag `test_suite` no arquivo `setup.py`. Desse modo, quando executamos os testes, o módulo python `Unittest` percorre o diretório `test`, carrega automaticamente todos os arquivos com sufixo `_test` e executa os testes dentro deles. Bacana não é? + +### Criando testes para a classe Circulo +Para encerrarmos o tutorial, vamos agora implementar os testes da classe Círculo. + +```python +# -*- coding: utf-8 -*- +import math +from unittest import TestCase +from codigo_avulso_test_tutorial.circulo import Circulo + +class TestCirculo(TestCase): + + def setUp(self): + TestCase.setUp(self) + self.fig = Circulo() + + def test_get_area(self): + # Utilizamos a formula diretamente por conveniencia + # já que math.pi e double e sendo assim, possui + # muitas casas decimais + self.fig.raio = 2 + area = math.pi * self.fig.raio**2 + self.assertEqual(self.fig.get_area(), area) + + self.fig.raio = 7.0 + area = math.pi * self.fig.raio**2 + self.assertEqual(self.fig.get_area(), area) + + def test_get_perimetro(self): + self.fig.raio = 2 + perimetro = 2 * math.pi * self.fig.raio + self.assertEqual(self.fig.get_perimetro(), perimetro) + + self.fig.raio = 7.0 + perimetro = 2 * math.pi * self.fig.raio + self.assertEqual(self.fig.get_perimetro(), perimetro) + +``` + +E agora a classe Circulo: + +```python +# -*- coding: utf-8 -*- +import math +from figura_geometrica import FiguraGeometrica + +class Circulo(FiguraGeometrica): + + def __init__(self): + self.raio = 0 + + # Retorna a area do circulo + def get_area(self): + return math.pi * self.raio**2 + + # Retorna o perimetro do circulo + def get_perimetro(self): + return 2 * math.pi * self.raio + +``` + +Finalmente, rodamos os testes agora com a presença da classe circúlo: + +```bash +python setup.py test +``` + +Se tudo estiver certo, teremos a seguinte saída. + +``` +running test +running egg_info +writing codigo_avulso_test_tutorial.egg-info/PKG-INFO +writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt +writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt +reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt' +running build_ext +test_get_area (test.quadrado_test.TestQuadrado) ... ok +test_get_perimetro (test.quadrado_test.TestQuadrado) ... ok +test_get_area (test.figura_geometrica_test.TestFiguraGeometrico) ... ok +test_get_perimetro (test.figura_geometrica_test.TestFiguraGeometrico) ... ok +test_get_area (test.circulo_test.TestCirculo) ... ok +test_get_perimetro (test.circulo_test.TestCirculo) ... ok + +---------------------------------------------------------------------- +Ran 6 tests in 0.001s + +OK + +``` + +### Conclusão +Com os testes ok, só nos resta subir o código para o github: + +``` +git add --all +git commit -m "[NEW] Adicionado classes e testes" +git push origin master +``` + +Esse tutorial ficou bem extenso, mas espero que tenha sido útil pra vocês. No próxima parte do tutorial, vamos ver como configurar o Travis, para que ele execute nossos testes quando realizarmos um `push` ou um `pull request` para o github. Também veremos o Coveralls que emite relatórios do quando do seu código está coberto por testes, algo muito interessante para ver se um *software* é bem testado. + +Os testes que escrevemos foram bem simples, apenas para fim de exemplo. Porém em uma aplicação séria, deve-se ter cuidado na hora de escrever os testes, de maneira a garantir que todas as possibilidades de erros sejam cobertas. A filosofia do TDD de escrever os testes antes do código da nossa aplicação, é algo que exige prática. Eu mesmo ainda não me sinto completamente a vontade seguindo esse fluxo de trabalho. Mas, escrever os testes primeiro te ajuda a manter seu código coerente e funcional, pois vocẽ vai precisar fazê-lo passar pelos testes. + +É isso pessoal. Obrigado por ler até aqui. Até a próxima postagem! + +**Publicado originalmente:** [python-com-unittest-travis-ci-coveralls-e-landscape-parte-1-de-4](https://mstuttgart.github.io/2016/04/12/2016-04-12-python-com-unittest-travis-e-coveralls-parte-1-de-4/) diff --git a/content/python-com-unittest-travis-e-coveralls-parte-2-de-4.md b/content/python-com-unittest-travis-e-coveralls-parte-2-de-4.md new file mode 100644 index 000000000..417bcee55 --- /dev/null +++ b/content/python-com-unittest-travis-e-coveralls-parte-2-de-4.md @@ -0,0 +1,164 @@ +Title: Python com Unittest, Travis CI, Coveralls e Landscape (Parte 2 de 4) +Slug: python-com-unittest-travis-ci-coveralls-e-landscape-parte-2-de-4 +Date: 2016-05-08 20:34:44 +Category: Python +Tags: git, travis-ci, python, coveralls, landscape, test, tutorial +Author: Michell Stuttgart +Email: michellstut@gmail.com +Github: mstuttgart +Linkedin: mstuttgart +Site: https://mstuttgart.github.io + +Fala pessoal, tudo bem? + +Na [primeira](python-com-unittest-travis-ci-coveralls-e-landscape-parte-1-de-4.html) parte deste tutorial, aprendemos como criar testes para nosso projeto. Nesta segunda parte, vamos configurar o serviço Travis CI para que o mesmo execute os testes do nosso projeto diretamente no github. Isso é especialmente útil quando possuímos várias pessoas trabalhando em um mesmo projeto, pois o `Travis CI` executa os testes após cada *push* ou *pull request*. Dessa forma garantimos que um determinado *commit* não irá "quebrar" nossa aplicação. + +Antes de inicarmos nosso trabalho de configuração do `Travis CI`, vamos aprender um pouco mais sobre esse serviço. + +#### Sobre o Travis CI + +[Travis CI](https://travis-ci.org/) é uma ferramenta online que permite executar o *deploy* de sua aplicação, rodando de maneira automática os testes do seu projeto hospedado no [Github](https://github.com/). Através dele é possível manter um histórico dos testes para cada *commit* do seu projeto, executar testes em paralelo, além do suporte a diversas linguagens de programação. Você pode, por exemplo, verificar se seu projeto funciona corretamente tanto com Python 2.7, quanto com o Python 3. + +Após a execução do teste, recebemos um email nos informando se o teste foi bem sucedido ou se houve alguma falha. O serviço é totalmente gratuito para projetos *opensource*. + +
+![Alt Text](images/mstuttgart/snapshot_24.png) +
+ +#### Criando uma conta + +Para utilizarmos o `Travis CI` em nosso projeto, precisamos primeiro realizar nosso cadastro no serviço. Para isso acesse [https://travis-ci.org/](https://travis-ci.org/). + +Logo no topo direito da página, temos o botão abaixo, para nos inscrevermos usando nossa conta no Github. + +
+![](images/mstuttgart/snapshot_25.png) +
+ +Ao pressionar o botão, você será direcionado para a página a seguir: + +
+![](images/mstuttgart/snapshot_26.png) +
+ +Realize o login com seu usuário/senha do `Github`. Assim que realizar o login, na canto superior direito da tela, clique no seu nome de usuário e, em seguida, em `"Accounts"`. Com isso, uma tela com todos os repositórios que você tem permissão de escrita (repositórios pessoais, de organização, forks e etc) será exibida. + +
+![](images/mstuttgart/snapshot_27.png) +
+ +Agora vamos ativar o serviço para o repositório que criei na primeira parte do tutorial: [codigo-avulso-test-tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial). Basta clicar no botão "X" ao lado esquerdo do nome do seu repositório. Ele ficará assim: + +
+![](images/mstuttgart/snapshot_28.png) +
+ +Bom, a partir de agora, seu repositório está pronto para o usar o `Travis CI`, porém antes precisamos configurar os parâmetros de teste do nosso projeto. + +#### Configurando o Travis CI em nosso repositório + +No diretório raiz do nosso projeto, vamos criar um arquivo chamado `.travis.yml`. + +```bash +touch .travis.yml +``` + Observe que o nome do arquivo obrigatoriamente deve inciar com ponto. Após criarmos o arquivo, teremos a seguinte estrutura de diretórios: + +``` +. +├── codigo_avulso_test_tutorial +│   ├── circulo.py +│   ├── circulo.pyc +│   ├── figura_geometrica.py +│   ├── figura_geometrica.pyc +│   ├── __init__.py +│   ├── __init__.pyc +│   ├── quadrado.py +│   └── quadrado.pyc +├── codigo_avulso_test_tutorial.egg-info +│   ├── dependency_links.txt +│   ├── PKG-INFO +│   ├── SOURCES.txt +│   └── top_level.txt +├── LICENSE +├── README.md +├── setup.py +├── test +│   ├── circulo_test.py +│   ├── circulo_test.pyc +│   ├── figura_geometrica_test.py +│   ├── figura_geometrica_test.pyc +│   ├── __init__.py +│   ├── __init__.pyc +│   ├── quadrado_test.py +│   └── quadrado_test.pyc +└── .travis.yml +``` + +Esse é nosso arquivo de configuração. Nele vamos adicionar qual linguagen nosso projeto utiliza, de quais módulos e pacotes ele depende, entre outras inúmeros ajustes, dependendo do seu projeto. Aqui, vou mostrar as configurações básicas que utilizo, para que o tutorial não fique muito extenso. Então, abra o arquivo `.travis.yml` em seu editor preferido e adicione o seguinte código. + +```travis +language: python + +python: + - "2.7" + +sudo: required + +script: + - python setup.py test + +``` + Agora vamos explicar cada tag do arquivo: + +* `language`: podemos definir qual linguagem nosso projeto utiliza. Se este parâmetro não for incluso, o `Travis CI` irá considerar a linguagem `ruby` como *default*. +* `python`: aqui definimos que os testes serão executados usando o Python 2.7 e se desejarmos, também podemos adicionar outras versões do Python. +* `sudo`: usado para executar o `Travis CI` como permissão de usuário `root`. Necessário caso você deseje instalar alguma dependência usando o comando `apt-get install nomepacote`. +* `script`: nessa `tag`, finalmente vamos executar nosso *script* de teste. + +Dica: neste [link](http://lint.travis-ci.org/) você pode colar o código do seu arquivo `.travis.yml` para verificar se o mesmo está correto. + +### Adicionado uma badge para o repositório +O próximo passo é é adicionar uma `badge` para o nosso repositório. Isso não é obrigatório, mas ajuda você, sua equipe e outras pessoas que se interessarem pelo seu repositório, a visualizar o *status* da execução dos testes e verificar se seu código está funcionando corretamente. + +
+[![Build Status](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial.svg?branch=master)](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial) +
+ +Na tela onde você ativou o `Travis CI` para seu reposiorio, clique no símbolo da engrenagem. + +
+![](images/mstuttgart/snapshot_28.png) +
+ +Na nova tela, podemos realizar algumas configurações, como por exemplo se o `Travis CI` será executado para *push* e para *pull requests* e também podemos pegar a `badge`. Ao clicarmos no botão logo ao lado do nome do repositório, uma janela será exibida. + +
+![](images/mstuttgart/snapshot_30.png) +
+ +Selecione a *branch* a ser observada pelo Travis CI, escolha a opção `Markdown` e copie o código que aparecerá na caixa de texto para o arquivo `README.md` do seu repositório. O meu `README.md` ficou assim: + +```markdown +# Codigo Avulso Test Tutorial +[![Build Status](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial.svg?branch=master)](https://travis-ci.org/mstuttgart/codigo-avulso-test-tutorial) + +``` + +Com esses passos, quando algum *push* ou *pull request* for enviado ao repositório, o `Travis CI` executará os testes, garantindo assim o funcionamento estável do nosso código e nos avisando caso alguma modificação venha causar algum erro em nossa aplicação. + +
+![](images/mstuttgart/snapshot_31.png) +
+ +Vale lembrar que o tempo para *deploy* pode variar, dependendo da quantidade de testes do seu projeto, quantidade de dependências a serem instaladas e etc. + +#### Conclusão + +Aqui encerramos a segunda parte do nossa série de tutoriais sobre `Unittest`. Eu decidi separar a série em 4 partes para que cada uma fosse explicada com mais detalhes mas sem deixar o tutorial muito extenso. O `Travis IC` ainda possui muitas configurações não abordadas aqui, então se você se interessar, pode dar uma olhada na sua documentação oficial [aqui](https://docs.travis-ci.com/). No próximo tutorial veremos como utilizar o `Coveralls` para gerar relatórios dos nossos testes. + +É isso pessoal. Obrigado por ler até aqui! + +Até o próximo tutorial! + +**Publicado originalmente:** [python-com-unittest-travis-ci-coveralls-e-landscape-parte-2-de-4](https://mstuttgart.github.io/2016/04/19/2016-04-19-python-com-unittest-travis-e-coveralls-parte-2-de-4/) diff --git a/content/python-webassets-elm.md b/content/python-webassets-elm.md new file mode 100644 index 000000000..b5d41117c --- /dev/null +++ b/content/python-webassets-elm.md @@ -0,0 +1,124 @@ +Title: Python webassets & Elm +Date: 2016-06-14 17:25 +Tags: elm, webassets, flask, django +Category: Webassets +Slug: python-webassets-elm +Author: Eduardo Cuducos +About_author: Sociólogo, geek, cozinheiro e fã de esportes. +Email: cuducos@gmail.com +Github: cuducos +Site: http://cuducos.me +Twitter: cuducos +Linkedin: cuducos + +Se você é geek e me conhece, ou se me segue nas redes sociais, já ouviu eu falar de [Elm](http://elm-lang.org/). É uma solução para _front-end_ com componentes reativos — mas Elm não é JavaScript. É uma outra linguagem, outro ambiente, outro compilador etc. + +É uma linguagem que muito me impressionou. Sou novato, engatinhando, tentando levantar e tomando belos tombos. Mas hoje resolvi um desses tombos: como integrar o Elm que uso para _front-end_ com _back-ends_ em Python. + +A resposta foi o [webassets-elm](https://github.com/cuducos/webassets-elm) — pacote que escrevi hoje e já está [disponível no PyPI](https://pypi.python.org/pypi/webassets-elm). + +Nesse texto vou fazer uma pequena introdução sobre interfaces reativas, sobre Elm em si, e depois explico um pouco do problema que o [webassets-elm](https://github.com/cuducos/webassets-elm) resolve — spoiler: é gambiarra. + +## O que é um _front-end_ com componente reativo? + +Componentes reativos são elementos na interface do usuário que seguem a [programação reativa](https://en.wikipedia.org/wiki/Reactive_programming): “um paradigma de programação orientado ao fluxo de dados e a propagação de mudanças” — como a Wikipédia define. + +Mas o que isso quer dizer? Comecemos com um exemplo básico **não** reativo: + +```python +a = 40 +b = 2 +c = a + b +print(c) # 42 + +a = 11 +print(c) # 42 +``` +Se esse bloco fosse reativo, ao mudar o valor de `a`, a alteração deveria também mudar o valor de `c` — ou seja, o segundo `print(c)` deveria resultar em `13`, não em `42`. + +Isso é muito útil quando gerenciamos interfaces complexas no _front-end_: ao invés de gerenciarmos vários `div`, `span` com suas classes e conteúdos, definimos uma estrutura de dados e as _regras_ para renderização desses dados em HTML. Alterando os dados, o HTML é atualizado automaticamente. + +Isso seria uma carroça de lerdeza se tivéssemos que atualizar o [DOM](https://pt.wikipedia.org/wiki/Modelo_de_Objeto_de_Documentos) cada vez que nossos dados fossem alterados — afinal [não é o JavaScript que é lento, o DOM é que é](https://www.youtube.com/watch?v=hQVTIJBZook). Por isso mesmo todos as alternativas para _front-end_ reativo — [Elm](http://elm-lang.org/), [React](https://facebook.github.io/react/), [Vue](https://vuejs.org/) e muitas outras — trabalham com um DOM virtual: todas as alterações são feitas primeiro nesse (eficiente) DOM virtual, que é comparado com o DOM real e então apenas as alterações mínimas são feitas no (lento) DOM real para que a interface seja atualizada. Simples assim. + +## Por quê Elm? + +Mas por quê Elm? Se praticamente a única linguagem que roda em navegador é JavaScript, que sentido faz aprender Elm? Elm é mais fácil que JavaScript? Essas são perguntas com as quais me habituei. Então vou falar aqui em linhas gerais o que normalmente respondo. + +Não posso negar que JavaScript é mais fácil de aprender — no sentido de que a curva de aprendizado é bem menor. Só que daí até escrever JavaScript de qualidade tem um abismo de distância (alô, _[technical debt](https://medium.com/@joaomilho/festina-lente-e29070811b84#.80xxnrf4f)_). + +O que eu gostei no Elm é que, apesar de a curva de aprendizado ser muito maior que a do JavaScript, a linguagem já te força a escrever código com certa qualidade. Por exemplo: + +* **Interface reativa de acordo com “melhores práticas”**: pensar na arquitetura do código é totalmente opcional no JavaScript, mas escrever algo com qualidade vai requerer que você aprenda JavaScript (sem [jQuery](http://jquery.com)), como usar [JavaScript de forma funcional](https://www.youtube.com/playlist?list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84), [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), [React](https://facebook.github.io/react/), [Redux](http://redux.js.org) ou ainda [Vue](https://vuejs.org/), para dar alguns exemplos. Então, se juntar a curva de aprendizado de todas coisas, vai ser uma curva de aprendizado parecida com a do próprio Elm (que já é funcional pois é um [Heskell](https://www.haskell.org) simplificado, já tem [sua própria arquitetura](http://guide.elm-lang.org/architecture/index.html) etc.) +* **Erros**: Com JavaScript (incluindo jQuery, ReactJs, Vue etc.) pode acontecer de passar erros para a produção ou homologação — um caso raro no qual uma função espere `x`, mas receba `y`, um loop infinito, uma função ou variável não definida, um objeto não encontrado. Com Elm, não: o compilador já elimina trocentos mil possibilidades de erro na compilação (como dizem na home do Elm, no _runtime exceptions_). Isso porquê o Elm já trabalha com tipagem e com objetos imutáveis, e consegue verificar possibilidades que o cérebro humano demoraria para imaginar. Se tem alguma possibilidade de teu código receber `String` quando espera `Integer`, ou de cair em um `import` cíclico, ele não compila se você não corrigir. Ele é chato, mas não deixa passar erro. +* **Mensagens de erro**: Se o Elm é chato por não compilar se houver possibilidade de erro, suas mensagens não são nada chatas. Na minha opinião uma das coisas mais chatas de desenvolver com JavaScript é que a console é muito ruim: as mensagens são vagas, é o terror do `undefined is not a function`, `NaN` etc. Já as mensagens de erro do compilador do Elm são muito educativas, te mostram o que está errado, onde está errado e, muitas vezes, como resolver. + +![Compiler errors for humans](http://elm-lang.org/assets/blog/error-messages/0.15.1/naming.png) + +Por fim, o JavaScript é muito verboso. Muito. Elm é mais conciso. Sem contar que ninguém vai se perder tentando descobrir se tem que fechar o parênteses antes das chaves, ou depois do ponto-e-vírgula. + +Enfim, se se interessam por Elm, além dos links que coloquei no texto, sugiro mais esses (todos em inglês): + +* [Elm em 7 minutos](https://egghead.io/lessons/elm-elm-in-5-minutes) +* [Por quê Elm — parte 1](http://ohanhi.github.io/master-elm-1-why-elm.html) +* [Introdução ao Elm](https://youtu.be/3_M2G9U51GA) + +## Webassets & webassets-elm + +Para quem não conhece, o [webassets](http://webassets.readthedocs.io/) é pacote muito utilizado no mundo Python para compilar, dar um _minify_ e comprimir CSS, JS etc. Por exemplo ele tem filtros que transformam o todos os [SASS](http://sass-lang.com) em CSS e, depois, junta tudo em um único `.css` bem compacto. + +A integração com [Flask](http://flask.pocoo.org) ou [Django](http://djangoproject.com) é super fácil e útil com o [flask-assets](http://flask-assets.readthedocs.io/) ou [django-assets](http://django-assets.readthedocs.org/). Com isso sua própria aplicação gera, no servidor, seus _assets_. Em ambiente de desenvolvimento e produção a geração dos _assets_ passa a ocorrer automaticamente (sem necessidade de _watchers_ ou de rodar manualmente `sass`, `coffee`, `browserify`, `webpack`, `grunt`, `gulp` etc.). + + +O [webassets-elm](https://github.com/cuducos/webassets-elm) nada mais é, então, do que um filtro para o _webassets_ saber o que fazer com arquivos `.elm` — ou seja para transformar meus arquivos em Elm em `.js` para o navegador saber o que fazer com eles (isso é o que chamamos de _compilar_ no Elm). Parece simples, e a arquitetura do _webassets_ ajuda muito: eles mesmos oferecem um objeto [`ExternalTool`](https://github.com/miracle2k/webassets/blob/master/src/webassets/filter/__init__.py#L400-L456) para facilitar a criação de filtros personalizados. + + +O que quero é que na hora que eu rodar minha aplicação em Flask ou em Django, se eu tiver alterado qualquer arquivo `.elm` (ou `.sass`), por exemplo, automaticamente a aplicação já gere um `.js` (ou `.css`) atualizado. + +O problema é que toda a arquitetura do _webassets_ é pensada tendo o [`stdout`](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29) como padrão. E o `elm-make` (comando que compila os arquivos Elm) [só grava em arquivo, não joga o resultado para o `stdout`](https://github.com/elm-lang/elm-make/issues/39). + +Faz sentido o _webassets_ ser assim: muitas vezes o que interessa é só o resultado das compilações, já que esses resultados podem ser processados novamente (um _minify_, por exemplo) antes de se juntar a outros resultados para, finalmente, salvar um asset `.css` ou `.js`. + +Então, a única complicação no _webassets-elm_ é essa — aí mora a famosa _gambiarra_, o famoso _jeitinho brasileiro_ enquanto o `elm-make` não oferece uma forma de compilar para o `stdout`. + +### Estrutura de um filtro do _webassets_ + +Normalmente um [filtro para o _webassets_ é simples](https://webassets.readthedocs.io/en/latest/custom_filters.html), veja esse exemplo (simplificado) de [um filtro para utilizar o Browserify](https://github.com/renstrom/webassets-browserify/blob/master/webassets_browserify/__init__.py). + +```python +class Browserify(ExternalTool): + + name = 'browserify' + + def input(self, infile, outfile, **kwargs): + args = [self.binary or 'browserify'] + args.append(kwargs['source_path']) + self.subprocess(args, outfile, infile) +``` + +Basicamente dentro de `input(…)`, que recebe o arquivo de entrada (`infile`) e o arquivo de saída (`outfile`), definimos qual o binário a ser chamado (`browserify`, por padrão, no exemplo) e adicionamos os argumentos que queremos passar para o binário (`kwargs['source_path']`). Tudo muito parecido com o [`subprocess` nativo do Python](https://docs.python.org/3.5/library/subprocess.html). + +Em outras palavras, se o `source_path` for `/home/johndoe/42.sass`, é como se digitássemos `browserify /home/johndoe/42.sass` no terminal e o _webassets_ juntaria o resultado desse comando no arquivo final (`outfile`). + +### Estrutura do _webassets-elm_ + +Mas o `elm-make` não funciona assim. Ele gera uma arquivo. Se chamarmos `elm-make hello.elm` ele gera um `index.html` (com o JavaScript compilado dentro). Podemos gerar apenas um JavaScript usando o argumento `--output`. Por exemplo, podemos usar `elm-make hello.elm --output hello.js` e teríamos apenas o JavaScript compilado no arquivo `hello.js`. + +Por esse motivo o _webassets-elm_ precisou de [uma gambiarra](https://github.com/cuducos/webassets-elm/blob/master/webassets_elm/__init__.py#L25-L43). Primeiro ele chama o `elm-make` gravando um arquivo temporário: + +```python +tmp = mkstemp(suffix='.js') +elm_make = self.binary or 'elm-make' +write_args = [elm_make, kw['source_path'], '--output', tmp[1], '--yes'] +with TemporaryFile(mode='w') as fake_write_obj: + self.subprocess(write_args, fake_write_obj) +``` + +Depois usa o `cat` (ou `type`, no Windows) para jogar o conteúdo desse arquivo temporário para o `stdout`: + +```python +cat_or_type = 'type' if platform == 'win32' else 'cat' +read_args = [cat_or_type, tmp[1]] +self.subprocess(read_args, out) +``` + +Não sei se é a melhor solução, mas foi o que resolveu por enquanto. Qualquer palpite, crítica, _pull request_, [RT](https://twitter.com/cuducos/status/742698891343204353), estrelinha no GitHub, _issue_, contribuição é bem-vinda ; ) \ No newline at end of file diff --git a/content/python_generators.md b/content/python_generators.md new file mode 100644 index 000000000..1421f68a5 --- /dev/null +++ b/content/python_generators.md @@ -0,0 +1,162 @@ +Title: Python Generators +Date: 2016-02-19 16:15 +Tags: python,generator,generators +Author: Andre Almar +Email: andre@y7mail.com +Github: andrealmar +Site: http://andrealmar.com +Twitter: andre_almar +Linkedin: andrealmar +Category: Python + +```Publicado originalmente em:``` [http://andrealmar.com/2016/02/generators](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: +
+ +```python +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: + +```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: + +```python +>>> # 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. + +```python +>>> 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 (). + +```python +list_comprehension = [1,2,3,4,5,6,7,8] +``` + +```python +generator_expression = (1,2,3,4,5,6,7,8) +``` + +```python +>>> 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 + 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: +``` 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/pythonbrasil2021.md b/content/pythonbrasil2021.md new file mode 100644 index 000000000..6077b3c2e --- /dev/null +++ b/content/pythonbrasil2021.md @@ -0,0 +1,26 @@ +Title: Participe da Python Brasil 2021, a maior conferência de Python da América Latina +Slug: python-brasil-2021 +Date: 08-02-21 15:00 +Tags: pythonbrasil,evento,2021 +Author: Rafael Alves RIbeiro +Email: rafael.alves.ribeiro@gmail.com +Linkedin: rafael-alves-ribeiro +Github: rafpyprog +Category: Python + +*Python Brasil 2021, a maior conferência de Python da América Latina, acontecerá entre os dias 11 e 17 de outubro, reunindo pessoas desenvolvedoras, cientistas de dados e entusiastas da tecnologia.* + +![alt text](/images/rafpyprog/pybr-banner.jpeg "Python Brasil 2021") + +A [Python Brasil 2021](https://2021.pythonbrasil.org.br/), evento promovido pela comunidade brasileira de Python, traz nesta edição uma agenda imperdível para quem quer mergulhar no universo de uma das linguagens de programação mais utilizadas na atualidade. + +Uma das principais novidades deste ano é a trilha de atividades em espanhol, que vai trazer ao evento palestras de toda a América Latina, criando um ambiente de muita diversidade onde as comunidades Python de língua espanhola vão poder se conectar e trocar experiências. + +Em sua 17 edição, o evento será realizado de forma online, gratuito e aberto para qualquer pessoa de 11 a 17 de outubro. É esperado um público de mais de 10 mil participantes de todas as partes do Brasil e do mundo. +Nos sete dias de imersão, os participantes poderão contribuir para projetos de software livre, participar de treinamentos e adquirir novos conhecimentos com outras pessoas da comunidade. + +E que tal falar sobre aquele projeto inovador, dar dicas sobre carreira ou mostrar formas de usar a tecnologia para causar impacto social, tudo isso em um ambiente inclusivo, seguro e acolhedor?! Até o dia 12 de agosto você poderá [submeter sua proposta](https://docs.google.com/forms/d/e/1FAIpQLSfXA7KGJbmoE6BHRgWAtK8LlBTEULv8QTS8ffHLIKUgeiLkZA/viewform) para apresentação de palestras e tutoriais na maior conferência Python da América Latina. + +Você poderá acompanhar a Python Brasil pelo YouTube em tempo real e também participar de atividades e interagir com toda a comunidade pelo servidor do Discord criado especialmente para o evento. + +Não deixe de seguir o [perfil do evento no Instagram](https://instagram.com/pythonbrasil?utm_medium=copy_link) ou [Twitter](https://twitter.com/pythonbrasil) para ficar por dentro das últimas novidades! diff --git a/content/questoes-para-estudo-de-algoritmos.md b/content/questoes-para-estudo-de-algoritmos.md new file mode 100644 index 000000000..615920d0a --- /dev/null +++ b/content/questoes-para-estudo-de-algoritmos.md @@ -0,0 +1,82 @@ +Title: Questões para estudo de algoritmos +Slug: questoes-para-estudo-de-algoritmos +Date: 2022-10-17 15:00 +Category: Python +Tags: python, performance +Author: Eduardo Klosowski +Email: eduardo_klosowski@yahoo.com +Github: eduardoklosowski +Twitter: eduklosowski +Site: https://dev.to/eduardoklosowski +About_author: Mestre em computação aplicada, programador backend com conhecimento de DevOps + +Recentemente li o texto do Maycon Alves, ["3 algoritmos para você sua lógica"](https://mayconbalves.com.br/3-algoritmos-para-voc%C3%AA-sua-l%C3%B3gica/), onde são apresentados 3 problemas para treinar a lógica e escrita de algoritmos: [cálculo de fatorial](https://pt.wikipedia.org/wiki/Fatorial), [identificar se um número é primo](https://pt.wikipedia.org/wiki/N%C3%BAmero_primo), e [calcular os valores da sequência de Fibonacci](https://pt.wikipedia.org/wiki/Sequ%C3%AAncia_de_Fibonacci). São problemas interessantes, e após resolvê-los, pode-se fazer outras perguntas que levam a um entendimento mais profundo desses algoritmos. Eu recomendo que leiam o texto do Maycon primeiro e tentem implementar uma solução para esses problemas propostos, e com isso feito, vamos discutir um pouco sobre eles. + +## Analisando as soluções + +No texto do Maycon, tem uma dica sobre o problema da sequência de Fibonacci, onde é dito que ele pode ser resolvido usando recursividade ou *loops*. Vamos analisar essas opções. + +Uma solução recursiva pode ser implementada como a baixo. O código dessa solução é simples e se aproxima bastante da descrição matemática do problema. + +```python +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 2) + fibonacci(n - 1) +``` + +Enquanto uma solução iterativa (com *loop*) pode ser um pouco mais complicada de se ler: + +```python +def fibonacci(n): + a = 0 + b = 1 + for i in range(n - 1): + a, b = b, a + b + return b +``` + +Essas mesmas técnicas podem ser utilizadas para resolver o cálculo do fatorial. Onde uma implementação recursiva e outra iterativa podem ser vistas a baixo: + +```python +def fatorial(n): + if n <= 1: + return 1 + return n * fatorial(n - 1) +``` + +```python +def fatorial(n): + valor = 0 + for i in range(1, n + 1): + valor *= i + return valor +``` + +Com essas soluções implementadas, vem uma pergunta: Existe alguma diferença entre elas, além da diferença de como isso está expresso no código? Um primeiro teste que pode ser feito é de desempenho visando observar quanto tempo cada implementação leva para calcular a resposta. Os testes foram executados em um notebook com processador Intel Core i7-6500U CPU @ 2.50GHz, memória RAM DDR4 2133MHz, no Python 3.9.2 do Debian 11, desativamente o *garbage collector* do Python durante a execução das funções para ter um resultado com menos variação, apresentados como uma média de 10 execuções ([código utilizado](https://github.com/eduardoklosowski/blog/tree/main/content/2022-10-17-questoes-para-estudo-de-algoritmos)). + +O gráfico a baixo mostra o tempo de execução das implementações que calculam os valores da sequência de Fibonacci, onde é possível observar que a implementação iterativa mantém uma linearidade do tempo conforme vai se pedindo números maiores da sequência, diferente da implementação recursiva, que a cada valor quase dobra o tempo de execução. + +![Gráfico do tempo de execução Fibonacci](images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png) + +E a baixo pode-se observar o gráfico para as implementações que calculam o fatorial, que devido aos tempos serem mais baixos, possui uma variação um pouco maior, e é possível observar uma tendência de reta para as duas implementações, com a implementação recursiva tendo um ângulo um pouco mais íngreme, implicando em um algoritmo mais lento. + +![Gráfico do tempo de execução fatorial](images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png) + +A partir desses dois gráficos algumas perguntas podem ser feitas: Por que a implementação recursiva do Fibonacci apresentou uma curva que se aproxima de uma exponencial e não de uma reta como as demais? Qual a diferença para a implementação recursiva do fatorial que explicar isso? Implementações recursivas são sempre piores que as implementações iterativas, ou existem casos em elas superem ou se equivalem as iterativas? + +Saindo um pouco desses gráficos, outras questões podem ser levantadas, como: Existe mais algum aspecto ou característica além do tempo de execução (e facilidade de leitura do código) para a escolha entre uma implementação ou outra? Considerando que essas funções vão rodar em um servidor, existe alguma coisa que possa ser feita para melhorar a performance dessas funções, como reaproveitar resultados já calculados para se calcular novos resultados? Como isso poderia ser feito? Quais são as desvantagens dessa abordagem? + +Olhando para o problema de verificar se um número é primo, existe o [crivo de Eratóstenes](https://pt.wikipedia.org/wiki/Crivo_de_Erat%C3%B3stenes), ele é uma implementação eficiente? Existem casos em que ele pode ser uma boa solução ou não? O exemplo a baixo (retirado da Wikipédia) mostra o processo para encontrar todos os números primos até 120, existe alguma forma de adaptá-lo para executar diversas vezes reaproveitando o que já foi calculado, como sempre retornar o próximo número primo? + +![Exemplo do crivo de Eratóstenes](https://upload.wikimedia.org/wikipedia/commons/8/8c/New_Animation_Sieve_of_Eratosthenes.gif) + +## Considerações + +Se você nunca se deparou com perguntas desse tipo, seja bem-vindo a área de análise de algoritmos, onde após se ter uma solução, busca-se analisar e descrever o comportamento do algoritmo, e até a busca de algoritmos mais eficientes. E trazendo para o dia a dia de um desenvolvedor, essas questões podem ser a resposta do motivo do código funcionar muito bem no computador de quem desenvolveu, mas demorar muito ou apresentar problemas para rodar no servidor de produção, ou com o tempo (e crescimento do volume de dados) começar a dar problemas. + +Nesse artigo eu apenas levantei as perguntas, deixo que cada um busque as respostas, que existem. Sintam-se livres para me procurar para discutir alguma questão ou orientações para encontrar as respostas, seja nos comentários do texto no [dev.to](https://dev.to/eduardoklosowski/questoes-para-estudo-de-algoritmos-5dab) ou no [Twitter](https://twitter.com/eduklosowski). + +--- + +Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi. diff --git a/content/raspando-a-web-com-python-parte-1.md b/content/raspando-a-web-com-python-parte-1.md new file mode 100644 index 000000000..8008b571b --- /dev/null +++ b/content/raspando-a-web-com-python-parte-1.md @@ -0,0 +1,111 @@ +--- +title: Raspando a Web com Python: Introdução +slug: raspando-a-web-com-python-parte-1 +summary: É possível usar Python puro para processar XML e calcular os gastos da copa! Primeiro post numa série sobre raspagem de dados com Python, LXML e Scrapy (y otras cositas más). +date: 2015-07-08 12:40 -0300 +author: Capi Etheriel +about_author: Capi Etheriel é membro da rede Transparência Hacker e adora trabalhar com Web Scraping. +site: https://barraponto.blog.br +email: barraponto@gmail.com +github: barraponto +twitter: barraponto +category: Python +tags: python,blog,scraping +--- + +Todo projeto que deseja raspar dados da web se resume ao seguinte *loop*: + +1. Fazer uma requisição para uma URL; +2. Processar a resposta (HTML, XML ou JSON); +3. Extrair os dados; +4. Deduzir as próximas URLs a visitar; +5. Repetir o loop. + +A parte mais difícil aqui é processar o HTML. É um processo todo delicado, o +HTML é cheio de detalhes para tolerar tags que não fecham, símbolos largados +nos lugares errados e por aí vai. Por isso é difícil ter um *parser* +(processador) de HTML nas bibliotecas-padrão de qualquer linguagem, seja +Python, Ruby, Javascript ou PHP. Já XML e JSON são formatos muito mais estritos +e tem parsers nativos em qualquer linguagem. + +Vamos ver como pegar os gastos da Copa do Mundo 2014, [expostos em +XML](http://www.portaltransparencia.gov.br/copa2014/api/rest/empreendimento). +Alguns browsers vão exibir o XML como uma árvore, facilitando a visualização da +estrutura -- meu browser favorito, o +[Firefox](https://www.mozilla.org/firefox/), faz isso. + +Nesse XML você pode ver um elemento maior, o `collection`, com muitos elementos +`copa:empreendimento` dentro. Esse prefixo `copa:` corresponde a um +*namespace*, um recurso do XML para poder misturar elementos de vocabulários +distintos. É importante prestar atenção nisso para podermos informar o nosso +parser de XML. + +Para começar o loop, vamos carregar a URL e popular uma árvore de elementos -- +uma abstração do Python para podermos manipular mais facilmente esses dados: + +```python +from xml.etree import ElementTree +from urllib.request import urlopen + +data_url = "http://www.portaltransparencia.gov.br/copa2014/api/rest/empreendimento" + +with urlopen(data_url) as datafile: + data = ElementTree.parse(datafile) +``` + +Repare como carregar uma URL no Python 3 tem uma sintaxe confortável, idêntica +à sintaxe de abrir arquivos. Agora pra seguir o loop, vamos extrair o que nos +interessa: o gasto (`valorTotalPrevisto`) de cada empreendimento iniciado ou +concluído (cujo `andamento` não esteja no estado `1`, `Não iniciado`). + +```python +spending = [float(element.find('./valorTotalPrevisto').text) + for element in data.iterfind('.//copa:empreendimento', + namespaces={'copa': data_url[:46]}) + if element.find('./andamento/id').text != '1'] +``` + +Pegar elementos de um `ElementTree` é fácil usando o método `iterfind` (retorna +um iterável, pra usar com for) ou `findall` (retorna uma lista propriamente +dita). Já pegar o conteúdo de um elemento exige apenas chamar o atributo +`.text`. Fácil, não? + +Isso daria certo se os dados fossem consistentes, mas... outro porém! O XML é +estrito -- as tags fecham, os símbolos estão no lugar certo, está tudo certo, +mas **os dados em si não são consistentes**. Nem todo elemento +`copa:empreendimento` tem um elemento `valorTotalPrevisto` dentro. E agora? + +É simples: vamos encapsular o processamento desse valor em um método simples, +que retorna zero quando não existe valor total previsto (pra facilitar a soma, +depois). + +```python +def get_cost(element): + cost = element.find('./valorTotalPrevisto') + return 0 if (cost is None) else float(cost.text) +``` + +Agora basta chamar o `get_cost` na nossa compreensão de lista: + +```python +spending = [get_cost(element) + for element in data.iterfind('.//copa:empreendimento', + namespaces={'copa': data_url[:46]}) + if element.find('./andamento/id').text != '1'] +``` + +E aí podemos finalmente somar todos os valores encontrados e imprimir usando o +poderoso método `format` do Python +([estude!](http://python.pro.br/material/cartao-format.pdf)). + +```python +print('Foram gastos {total:.2f} dinheiros do governo brasileiro'.format( + total=sum(spending))) +``` + +Bônus: Uma versão mais idiomática (PYTHONICA) do código está disponível no meu +[Gist](https://gist.github.com/barraponto/21c705006635a1a72407). Fique à vontade +para contribuir, comentar, melhorar, etc :) + +Agradecimentos ao Fernando Masanori que [começou a brincadeira com esses +dados](https://gist.github.com/fmasanori/c648d753e7d0176ff172)! diff --git "a/content/salvando-gr\303\241fico-de-contribui\303\247\303\265es-do-Github-com-Python-e-Selenium.md" "b/content/salvando-gr\303\241fico-de-contribui\303\247\303\265es-do-Github-com-Python-e-Selenium.md" new file mode 100644 index 000000000..f6c2fc6cb --- /dev/null +++ "b/content/salvando-gr\303\241fico-de-contribui\303\247\303\265es-do-Github-com-Python-e-Selenium.md" @@ -0,0 +1,176 @@ +Title: Salvando gráfico de contribuições do Github com Python e Selenium +Date: 2016-02-11 11:47:44 +Tags: python, selenium, github +Slug: salvando-grafico-github-python-selenium +Author: Othon Alberto +Site: http://www.othonalberto.com.br +Email: othonalberto@gmail.com +Github: othonalberto +Facebook: othonnn + + + +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"](http://alexrecker.com/using-selenium-buy-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](http://selenium-python.readthedocs.org/), 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](https://github.com/othonalberto/ghss). + +----- + +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. + +```python +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. + +```python +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. + +```python +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. + +```python +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. + +```python +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. + +```python +driver.get("https://github.com/%7B0%7D" .format(USERNAME)) +``` + +Por exemplo, se fizermos o seguinte código: + +```python +print("Meus esportes preferidos são: {0}, {1} e {2}" .format("futebol", "basquete", "corrida")) +``` + +O resultado será: + +```python + 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"```. + +```python +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](images/othonalberto/2016-01-24_gitshot_othonalberto.png "Resultado") + +----- +Código completo: + +```python + + +#!/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/%7B0%7D" .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! + + + + diff --git a/content/sites_estaticos_com_lektor.md b/content/sites_estaticos_com_lektor.md new file mode 100644 index 000000000..a0511b810 --- /dev/null +++ b/content/sites_estaticos_com_lektor.md @@ -0,0 +1,134 @@ +Title: Sites Estáticos com Lektor +Slug: sites-estaticos-com-lektor +Date: 2016-04-30 12:00 +Tags: tutorial,lektor,blog,framework +Category: Lektor +Author: Humberto Rocha +Email: humrochagf@gmail.com +Github: humrochagf +Twitter: humrochagf +Facebook: humrochagf +Linkedin: humrochagf + +**Publicado originalmente em:** [humberto.io/2016/4/sites-estaticos-com-lektor](http://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](https://pages.github.com) eu descobri o [Pelican](http://blog.getpelican.com) por indicação do [Magnun Leno](http://mindbending.org/pt) 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](https://telegram.me/pythonbr) me citam o Lektor e aí começou a aventura. + +## Lektor? + +[Lektor](https://www.getlektor.com) é um gerenciador de conteúdo estático criado por [Armin Ronacher](http://lucumr.pocoo.org) (sim, o criador do [flask](http://flask.pocoo.org)) que permite a criação de websites a partir de arquivos de texto. + +## Porque usar? + +Como descrito no próprio [site](https://www.getlektor.com/docs/what) 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](http://localhost:5000) para ver o resultado: + +![meusite](images/humrochagf/meusite.png) + +### 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](http://localhost:5000/admin) + +![meusite-admin](images/humrochagf/meusite-admin.png) + +## 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`: + +```ini +[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](https://github.com/humrochagf/humrochagf.github.io) para ter uma idéia. + +Para informações mais detalhadas você pode acessar a [documentação do lektor](https://www.getlektor.com/docs) e também ficar de olho nas próximas postagens. diff --git a/content/six.md b/content/six.md index 150f2fdd5..7fab05c32 100644 --- a/content/six.md +++ b/content/six.md @@ -1,6 +1,6 @@ Title: Sobre o six e como ele ajuda a escrever código compatível com python 2 e 3 Date: 2014-05-04 22:21 -Tags: python, six, compatibility, python3 +Tags: python, six, compatibility Category: Python Slug: sobre-o-six-e-como-ele-ajuda-a-escrever-codigo-compativel-com-python-2-e-3 Author: Artur Felipe de Sousa diff --git a/content/solucao-quase-definitiva-para-permissoes-em-projetos-django.md b/content/solucao-quase-definitiva-para-permissoes-em-projetos-django.md new file mode 100644 index 000000000..d21db135f --- /dev/null +++ b/content/solucao-quase-definitiva-para-permissoes-em-projetos-django.md @@ -0,0 +1,107 @@ +Title: Solução (quase) definitiva para permissões em projetos Django +Date: 2014-11-22 11:30 +Tags: Django, django-global-permissions, permissões +Category: Django +Slug: solucao-quase-definitiva-para-permissoes-em-projetos-django +Author: Eduardo Matos +Email: eduardo.matos.silva@gmail.com +Github: eduardo-matos + +De todas as tarefas que o Django se propõe a resolver, é possível que o módulo de permissões seja o mais gera dúvidas. Seu funcionamento é bastante simples, mas existe uma peça faltando no quebra-cabeça que o torna confuso para marinheiros de primeira viagem. + +## O que está faltando? + +Atualmente o Django suporta, de forma nativa, somente permissões baseadas em modelos. Então é possível atribuir ou remover a permissão criar/alterar/deletar um dado modelo. Essas permissões são criadas criadas automaticamente através da inspeção dos modelos usados na aplicação, bastando estar presente na tupla `INSTALLED_APPS`. + +O problema reside no fato de que, em geral, não nos interessa atribuir permissões a modelos, e sim criar permissões genéricas, como poder acessar uma página ou poder visualizar um item no menu por exemplo. Como este tipo de permissão não está atrelada a um modelo, em tese não é possível utilizá-la. + +## Qual a solução? + +Devido a ser um problema recorrente, publiquei um pacote no PyPI chamado [Django Global Permissions](https://pypi.python.org/pypi/django-global-permissions/0.1.0), que possibilita a criação de permissões genéricas, resolvendo o empecilho que mencionei anteriormente. + +Para utilizar esse pacote, basta instalá-lo e adicioná-lo à tupla `INSTALLED_APPS`. + +``` +pip install django-global-permissions +``` + +```python +INSTALLED_APPS += ('global_permissions',) +``` + +Caso você use o admin do Django, você pode acessar a seção do *Global Permissons*, e criar suas permissões genéricas informando `name` (descrição) e `codename`. O `codename` será utilizado sempre que for necessário verificar uma dada permissão. É altamente recomendável que o `codename` contenha somente *letras* e o caractere *underscore*. + +![Criando permissão](images/eduardo-matos/criando-permissao.png) + +Após criada a permissão, você pode associá-la a um usuário ou um grupo de usuários. Se quiser associar a um usuário, basta acessar a página de edição do mesmo, e na seção de permissões atribuí-la ao usuário, e por fim salvar. + +![Permissões de usuário](images/eduardo-matos/permissao-de-usuario.png) + +A permissão pode ser associada também a um grupo de usuários, e para isso basta acessar a página de um grupo específico, e associar a permissão da mesma maneira que faz com qualquer outra permissão no Django. + +## Limitando o acesso nas views + +Toda view recebe um `request` como parâmetro contendo uma referência ao usuário logado. Dessa maneira é possível verificar se este usuário tem uma dada permissão usando o método `has_perm`. + +```python +from django.core.exceptions import PermissionDenied + +def config_view(request): + if not request.user.has_perm('global_permissions.pode_acessar_pagina_config'): + raise PermissionDenied + + # continuar com o restante do processamento... +``` + +No exemplo acima, se o usuário tiver a permissão `pode_acessar_pagina_config` ou pertencer a um grupo que tenha essa permissão, então passará pelo `if` sem problemas, caso contrário receberá um erro de permissão negada. + +Também é possível verificar se um usuário tem mais de uma permissão sem a necessidade de um `if` com vários `and`, através do método [`has_perms`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.has_perms). + +Caso você prefira class based views (assim como eu), as alterações são pequenas em relação ao exemplo acima. Por padrão todas as class based views tem uma propriedade chamada `request`, que podemos usar acessar o usuário logado e fazer as verificações de permissão. + +```python +from django.view.generic import TemplateView +from django.core.exceptions import PermissionDenied + +class ConfigView(TemplateView): + template_name = 'config.html' + + def render_to_responde(self): + if not self.request.user.has_perm('global_permissions.pode_acessar_pagina_config'): + raise PermissionDenied + + # continuar com o restante do processamento... +``` + +Ainda é possível deixar o código mais simples através do pacote [django-braces](https://github.com/brack3t/django-braces). Com ele temos acesso ao [`PermissionRequiredMixin`](http://django-braces.readthedocs.org/en/latest/access.html#permissionrequiredmixin), que automaticamente faz a verificação de permissão para nós. + +```python +from django.view.generic import TemplateView +from brace.views import PermissionRequiredMixin + +class ConfigView(PermissionRequiredMixin, TemplateView): + template_name = 'config.html' + permission_required = 'global_permissions.pode_acessar_pagina_config' +``` + +Para mais informações sobre o django-braces, acesse sua [documentação oficial](http://django-braces.readthedocs.org/en/latest/index.html). + +## Limitando o acesso nos templates + +Limitar o acesso nos templates é tão simples quanto implementar nas views, mas diferentemente do primeiro, os templates já recebem automaticamente um objeto de permissões do usuário logado (desde que você utilize o *context processor* `django.contrib.auth.context_processors.auth`). Supondo que queiramos saber se o usuário pode visualizar um dado item do menu, podemos fazer da seguinte forma: + +```htmldjango +{% if perms.global_permissions.pode_acessar_pagina_config %} + Configuração +{% endif %} +``` + +## Isso resolve tudo? + +Não. + +Existem casos onde pode ser útil outro tipo de arquitetura para permissões. Se você precisar limitar o acesso baseado em um registro no banco de dados, então pode usar o [django-guardian](https://github.com/lukaszb/django-guardian). Se percisar de algo mais rebuscado, o [django-permission](https://github.com/lambdalisue/django-permission) talvez seja uma escolha mais acertada. O [django-global-permissions](https://github.com/eduardo-matos/django-global-permissions) tem como foco simplificar a criação e uso de permissões globais. Se é isso que você precisa, então não precisa mais procurar por uma solução ;) + +## Contribuindo + +Caso você encontre algum bug ou tenha uma ideia de como melhorar o projeto, fique a vontade para contribuir através do [repositório no GitHub](https://github.com/eduardo-matos/django-global-permissions)! diff --git a/content/tdd-com-python-e-flask.md b/content/tdd-com-python-e-flask.md new file mode 100644 index 000000000..de4bffee4 --- /dev/null +++ b/content/tdd-com-python-e-flask.md @@ -0,0 +1,695 @@ +Title: TDD com Python e Flask +Date: 2016-03-14 11:59 +Tags: tdd, test driven development, flask +Category: TDD +Slug: tdd-com-python-e-flask +Author: Eduardo Cuducos +About_author: Sociólogo, geek, cozinheiro e fã de esportes. +Email: cuducos@gmail.com +Github: cuducos +Site: http://cuducos.me +Twitter: cuducos +Linkedin: cuducos + +> Baseado na palestra que ofereci no [encontro](http://www.meetup.com/Grupy-SP/events/228437612/) do [Grupy-SP](https://groups.google.com/forum/#!forum/grupy-sp), em 12 de março de 2016. O código dessa atividade está disponível no [meu GitHub](https://github.com/cuducos/grupy-python-tdd-flask). + +A ideia desse exercício é introduzir a ideia de _test driven development_ (TDD) usando [Python](http://http://python.org) e [Flask](http://flask.pocoo.org/) — 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](http://virtualenv.readthedocs.org). + +> 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: + +```console +$ 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: + +```console +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](https://docs.python.org/3.5/library/unittest.html), 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: + +```python +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: + +```console +$ 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`: + +```python +def fatorial(numero): + if numero in (0, 1): + return 1 + return numero * fatorial(numero - 1) +``` + +E adicionar essa linha no topo do `tests.py`: + +```python +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: + +```python +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`; +1. 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`); +1. 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/](http://pythonclub.com.br/). +1. 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](https://pt.wikipedia.org/wiki/Lista_de_códigos_de_status_HTTP) é 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: + +```python +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 + from app import meu_web_app + File "/Users/cuducos/Desktop/flask/app.py", line 3, in + 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: + +```python +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](https://twitter.com/rochacbruno) + +``` +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: + +``` +@meu_web_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](http://ains.co/blog/things-which-arent-magic-flask-part-1.html). + +Para garantir que tudo está certinho mesmo, podemos adicionar mais um teste. Queremos que a resposta do servidor seja um HTML: + +```python +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()`? + +```python +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: + +```python +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: + +```python +if __name__ == "__main__": + meu_web_app.run() +``` + +Depois executamos o arquivo: + +```console +$ 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/](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: + +```python +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](http://henriquebastos.net) + +```python +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`: + +```python +@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`: + +```html + + + + Eduardo Cuducos + + +

Eduardo Cuducos

+

Sociólogo, geek, cozinheiro e fã de esportes.

+ + +``` + +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: + +```python +def test_content(self): + response_str = self.response.data.decode('utf-8') + self.assertIn('Eduardo Cuducos', str(response_str)) + self.assertIn('

Eduardo Cuducos

', str(response_str)) + self.assertIn('

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('Eduardo Cuducos', str(self.response.data)) +AssertionError: 'Eduardo Cuducos' 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: + +```python +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](http://getbootstrap.com/). 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: + +```python +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](http://getbootstrap.com/getting-started/#download-cdn). É só incluir essa linha no `` do nosso HTML: + +```html + +``` + +Agora, com os testes passando, vamos utilizar as classes do Bootstrap para formatar melhor nossa página. Vamos retirar nosso `

` e `

` e, ao invés disso, partir do componente [Jumbotron](http://getbootstrap.com/components/#jumbotron) fazendo algumas pequenas alterações: + +```html +

+
+ Eduardo Cuducos +

Eduardo Cuducos

+

Sociólogo, geek, cozinheiro e fã de esportes.

+

Me siga no Twitter

+
+
+``` + +Com essa página “incrementada” podemos ainda refinar nossos testes, garantindo que sempre temos a foto e o link: + +```python +def test_profile_image(self): + response_str = self.response.data.decode('utf-8') + self.assertIn('Me siga no Twitter', 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`: + +```python +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: + +```python +@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: + +```html + + + + {{ perfil.nome }} + + + +
+
+ {{ perfil.nome }} +

{{ perfil.nome }}

+

{{ perfil.descricao }}

+

Me siga no {{ perfil.nome_url }}

+
+
+ + +``` + +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`: + +```python +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: + +```python +@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('Eduardo Cuducos', str(response_str)) +AssertionError: 'Eduardo Cuducos' not found in '\n\n \n Eduardo Mendes\n \n \n \n
\n
\n Eduardo Mendes\n

Eduardo Mendes

\n

Apaixonado por software livre e criador de lambdas.

\n

Me siga no GitHub

\n
\n
\n \n' + +====================================================================== +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 '\n\n \n Eduardo Mendes\n \n \n \n
\n
\n Eduardo Mendes\n

Eduardo Mendes

\n

Apaixonado por software livre e criador de lambdas.

\n

Me siga no GitHub

\n
\n
\n \n' + +---------------------------------------------------------------------- +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()`: + +```python +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: + +```python +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('Eduardo Mendes', str(response_str)) + self.assertIn('

Eduardo Mendes

', str(response_str)) + self.assertIn('

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('Me siga no GitHub', 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: + +```pyhton +@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: + +```pyhton +@meu_web_app.route('/') +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: + +* Em português: [Tutorial](http://pythonclub.com.br/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python) (ainda incompleto) do [Bruno Rocha](https://twitter.com/rochacbruno) +* Em inglês: [Tutorial](http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) ou [livro](http://flaskbook.com) do [Miguel Grinberg](https://twitter.com/miguelgrinberg) + +Leitura recomendada para conhecer mais sobre TDD: + +* Em inglês: [Livro](http://shop.oreilly.com/product/0636920029533.do) do [Harry Percival](https://twitter.com/hjwp) +* Em inglês: essa [resposta](http://stackoverflow.com/questions/4904096/whats-the-difference-between-unit-functional-acceptance-and-integration-test/4904533#4904533) no Stack Overflow sobre _unit_, _integration_, _functional_ e _acceptance test_. + +> Quem aprendeu alguma coisa nova? +> +> — [Raymond Hettinger](https://twitter.com/raymondh) diff --git a/content/testes_de_carga_com_o_locust.md b/content/testes_de_carga_com_o_locust.md new file mode 100644 index 000000000..51d75b19e --- /dev/null +++ b/content/testes_de_carga_com_o_locust.md @@ -0,0 +1,359 @@ +Title: Testes de carga com o Locust +Slug: testes-de-carga-com-o-locust +Date: 2015-01-11 18:00 +Tags: locust,web,tutorial,test,load-testing +Author: Diego Garcia +Email: drgarcia1986@gmail.com +Github: drgarcia1986 +Site: http://www.codeforcloud.info +Twitter: drgarcia1986 +Linkedin: drgarcia1986 +Category: load-testing + + + +

+ +
+
+Quanto de carga sua aplicação web aguenta? Se conseguiu responder essa pergunta, como você fez para medir esse desempenho? Se você não conseguiu responder nenhuma das questões anteriores, ou apenas uma, ou até mesmo respondeu as duas mas em algum momento utilizou a palavra _complicado_ para descrever como testou, chegou a hora de resolver esse problema de uma forma muito simples. + + + +> Esse texto foi publicado originalmente em meu blog, no endereço [http://www.codeforcloud.info/](http://www.codeforcloud.info/). + +## Locust +O Locust é uma ferramenta open source escrita em python para testes de carga em aplicações web (independente da técnologia). A principal caracteristica do Locust é a forma como são escritos os testes, simples códigos python. Com poucas linhas de código é possível escrever testes de carga que vão realmente colocar sua aplicação em um campo de batalha. + +### Instalação +Para quem já usa python a facilidade de uso já começa na instalação, basta utilizar o comando ``pip install locustio`` e a instalação está feita. +Para instalar o Locust em um ambiente unix com _virtualenv_, basta criar o virtualenv: +```bash +user@machine:~/locust$ virtualenv venv +New python executable in venv/bin/python +Installing setuptools, pip...done. +``` +Ativar o virtualenv +```bash +user@machine:~/locust$ source venv/bin/activate +(venv)user@machine:~/locust$ +``` +E instalar o Locust +```bash +(venv)user@machine:~/locust$ pip install locustio +``` +Para confirmar se o Locust está instalado, use o comando ``locust`` com a opção ``-V`` +```bash +(venv)user@machine:~/locust$ locust -V +[2015-01-08 22:59:28,251] machine/INFO/stdout: Locust 0.7.2 +``` +> Não se preocupe se aparecerem mensagens de _warning_ alertando sobre a ausência do _zmq_, a ausência desse pacote não afeta nossa demostração. + +### Aplicação para testes +Para demonstrar a utilização do Locust, vamos criar um simples webservice que realiza conversões de tempo (por ex. _hora para segundo_). + +Na criação desse webservice, utilizaremos o **Flask** por ser um dos frameworks mais simples e utilizados atualmente. Como o Locust utiliza o Flask internamente, ele já está instalado em nosso virtualenv. + +> Como o foco do post não é falar do _Flask_, não entrarei em detalhes do framework, se você não está familiarizado com ele, recomendo a leitura deste [excelente artigo](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python) do Bruno Rocha. + +Crie um arquivo chamado **converter.py** com o seguinte código: +```python +from flask import Flask + + +converter = {'DH': lambda d: d * 24, # day to hours + 'HM': lambda h: h * 60, # hour to minutes + 'MS': lambda m: m * 60, # minute to seconds + 'DM': lambda d: d * 1440, # day to minutes + 'DS': lambda d: d * 86400, # day to seconds + 'HS': lambda h: h * 3600} # hour to seconds + +app = Flask(__name__) + +@app.route('//') +def conversion(rule, value): + try: + return str(converter[rule.upper()](value)) + except KeyError: + return "Rule for conversion not found", 404 + + +if __name__ == "__main__": + app.run() +``` +Para testar essa aplicação basta inicia-la: +```bash +(venv)user@machine:~/locust$ python converter.py + * Running on http://127.0.0.1:5000/ +``` +E realizar uma requisição: +```http +curl http://127.0.0.1:5000/hm/3 +180 +``` + +### Criando as _Locust Tasks_ +Agora que já temos o que testar, vamos finalmente escrever nosso script Locust. Como eu disse anteriormente, os scripts Locust são scripts python, sem nenhum segredo. + +Os testes são baseados em **Tasks** que são criadas em uma classe que herda da classe ``TaskSet`` do Locust. Na classe _TaskSet_ o que determina se um método é uma _task_ é a presença do decorator ``@task``. + +O Locust trabalha com o conceito de requests baseados em clientes com caracteristicas especificas. O principal atributo das classes de cliente _Locust_ é o atributo ``task_set``, que recebe a classe onde as tasks de teste estão especificadas. Como o foco é o teste de aplicações web, o protocolo em questão é o protocolo **HTTP**, sendo assim, a classe base para criação desses _clientes_ é a classe ``HttpLocust``. + +Não se assuste, como estamos falando de **Python**, a explicação é praticamente maior que o código :). + +Para testar alguns métodos de nosso webservice, crie um arquivo chamado **locust_script.py** com o código a seguir. + +```python +from locust import TaskSet, task, HttpLocust + +class ConverterTasks(TaskSet): + @task + def day_to_hour(self): + self.client.get('/dh/5') + + @task + def day_to_minute(self): + self.client.get('/dm/2') + + +class ApiUser(HttpLocust): + task_set = ConverterTasks + min_wait = 1000 + max_wait = 3000 +``` +No código acima, criamos a classe ``ConverterTasks`` onde especificamos nossas tasks para os testes através do decorator ``@task`` e a class ``ApiUser`` onde especificamos o nosso cliente Locust do tipo ``HttpLocust``, preenchendo o atributo ``task_set`` com a classe ``ConverterTask``. + +Como nosso cliente Locust é do tipo **HttpLocust**, foi possível utilizar o objeto ``self.client`` em nosso **task_set**. Note que o objeto _self.client_ da classe _ConverterTasks_ consiste em um cliente http. + +Os atributos ``min_wait`` e ``max_wait`` especificam o tempo mínimo e máximo em milisegundos que o teste deve aguardar entre a execução de uma task e outra. O valor padrão desses atributos é _1000_ (1 segundo). + +### Executando os testes +Com o script locust escrito, é chegada a hora da mágica, vamos finalmente ver o Locust em ação. Se certifique que seu webservice está no ar e inicie seu script Locust com o seguinte comando: +```bash +(venv)user@machine:~/locust$ locust -f locust_script.py –H http://127.0.0.1:5000 +``` +A opção ``-f`` específica o arquivo com script Locust e a opção ``-H`` específica o endereço do webservice que será testado. +Ao executar esse comando, o Locust será iniciado na porta **8089** (porta padrão que pode ser alterada através da opção ``-P``). + +Ao abrir no browser a url http://127.0.0.1:8089 será apresentada a seguinte tela: + +![locust](/images/drgarcia1986/locust_inicial.png) + +O campo **Number of users to simulate** é referente a quantidade de usuários simultâneos que serão utilizados para o teste, já o campo **Hatch rate** é referente a quantidade de usuários que serão adicionados ao teste por segundo (até atingir o numéro de usuários específicado na opção anterior). Específique as opções anteriores e clique em **Start swarming** para que os testes sejam iniciados e seja apresentada a seguinte tela. + +![locust](/images/drgarcia1986/locust.png) + +Talvez as informações mais importantes apresentadas nessa tela é o **RPS** (request per seconds) e os **failures**. +Note que os resultados são apresentados por cada _Task_ e são totalizados no final da listagem. + +### Definindo _peso_ para os teste +É possível determinar o _peso_ de uma _task_ através do parâmetro opcional **weight** do decarator ``@task``. Por exemplo, imagine que no cenário real são mais requisições para conversão de _dias para minutos_ do que de _dias para horas_, sendo assim nossos testes devem seguir essa mesma lógica. + +```python +from locust import TaskSet, task, HttpLocust + +class ConverterTasks(TaskSet): + @task(3) + def day_to_hour(self): + self.client.get('/dh/5') + + @task(6) + def day_to_minute(self): + self.client.get('/dm/2') + + +class ApiUser(HttpLocust): + task_set = ConverterTasks + min_wait = 1000 + max_wait = 3000 +``` + +Da forma como foi especificado, para cada requisição de conversão de _dia para horas_, serão executadas duas de _dia para minutos_. + +### Utilizando outros Verbos HTTP +Nesse nosso exemplo só utilizamos o método http _GET_, até mesmo porque nosso webservice só possui métodos GET, porém, é possível utilizar os outros verbos HTTP, por exemplo: + +```python +from locust import TaskSet, task, HttpLocust + +class RegistersTasks(TaskSet): + @task + def create_person(self): + self.client.post('/person', {'name': 'Foo', 'email': 'foo@bar.net'}) + + @task + def create_group(self): + self.client.post('/group', {'name': 'Bar'}) + +class WebsiteUser(HttpLocust): + task_set = RegistersTasks + min_wait = 1000 + max_wait = 3000 +``` + +O cliente HTTP presente no objeto ``self.client`` é baseado na biblioteca [Requests](http://docs.python-requests.org/en/latest/), sendo assim, os métodos http (GET, POST, PUT, DELETE, OPTIONS) estão disponiveis. + +### Testando com valores dinámicos +No teste do conversor de tempo, utilizamos valores fixos, porém, para se apróximar mais da realidade, o ideal seria testar com valores aleatórios. Como estamos falando de código Python, isso é muito simples, bastar alterar de: +```python +self.client.get('/dh/5') +``` +para: +```python +from random import randint + + +self.client.get('/dh/%d' % randint(1, 10)) +``` +Mas isso geraria um problema, pois o Locust agrupa o relatório de testes por url, como estamos realizando até 10 chamadas diferentes para o mesmo recurso, teriamos até 10 chamadas diferentes sendo listas e contabilizadas separadamente. + +![locust](/images/drgarcia1986/locust_random.png) + +Para resolver esse problema, podemos nomear os requests independente da url, atráves do parâmetro ``name`` dos métodos do client HTTP. Sendo assim nosso código poderia ficar da seguinte forma: + +```python +from locust import TaskSet, task, HttpLocust +from random import randint + +class ConverterTasks(TaskSet): + @task + def day_to_hour(self): + self.client.get('/dh/%d' % randint(1, 10), name='/dh/[int]') + + @task + def day_to_minute(self): + self.client.get('/dm/%d' % randint(1, 10), name='/dm/[int]') + + +class ApiUser(HttpLocust): + task_set = ConverterTasks + min_wait = 1000 + max_wait = 3000 +``` + +Com isso o relatório volta a ser apresentado da maneira esperada. + +![locust](/images/drgarcia1986/locust_name.png) + +### Sessão de usuário +O cliente http da classe ``HttpLocust`` preserva os cookies entre os requests, possibilitando realizar logins e consumir métodos remotos que dependem de uma sessão de usuário ativa. + +Para validar esse conceito, criaremos uma aplicação simples que possui login de usuário e um recurso protegido pela sessão. Somente o necessário para ver o Locust em ação. + +```python +from flask import Flask, session, request, redirect, url_for, abort + + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'a7b05c4e06fe0502af4a3d42dd41327b' + +users = {'john': {'password': 'mypass', 'name': 'John Lee'}, + 'bob': {'password': 'secret', 'name': 'Robert Brown'}} + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + user = users.get(request.form['username']) + if not user: + return 'User not found', 404 + if user['password'] != request.form['password']: + return 'Wrong password', 401 + session['user'] = user + return redirect(url_for('home')) + else: # GET + return ''' + +

User

+

Pass

+

+ + ''' + +@app.route('/logout') +def logout(): + session.pop('user', None) + return redirect(url_for('index')) + +@app.route('/home') +def home(): + if 'user' in session: + return ''' +

Welcome %s

+

For logout click here

+ ''' % (session['user']['name'], url_for('logout')) + else: + abort(401) + +@app.route('/') +def index(): + return ''' +

Flask with session :)

+

Click here to login page

+ ''' % url_for('login') + + +if __name__ == '__main__': + app.run() +``` +A aplicação representada no código acima consiste em uma página inicial (``/``), uma página de login (``/login``), uma página de logout (``/logout``) e uma página home do usuário (``/home``) que só está acessivel para usuários logados. Obviamente esse é só um exemplo didático. + +Se criarmos um script Locust para testar essa aplicação e nele não realizarmos o login do usuário, teriamos uma série de falhas para consumir o método remoto ``/home``. + +![locust](/images/drgarcia1986/locust_session_fail.png) + +Porém a classe ``TaskSet`` do Locust possui o método ``on_start`` que consiste no método que será executado (apenas uma vez) antes do cliente Locust iniciar as tasks. Será nele que iremos realizar o _login_ do usuário. + +```python +from locust import TaskSet, task, HttpLocust + + +class SessionTasks(TaskSet): + def on_start(self): + self.client.post('/login', {'username': 'john', + 'password': 'mypass'}) + + @task(1) + def home(self): + self.client.get('/home') + + @task(4) + def index(self): + self.client.get('/') + + +class WebsiteUser(HttpLocust): + task_set = SessionTasks + min_wai=1000 + max_wait=3000 +``` +Como estamos realizando o login do usuário sempre que o cliente Locust inicia suas _tasks_, os cookies de sessão já estarão armazenados nos controles do objeto ``self.client``, com isso, é possível testar até mesmo os métodos que dependem de autenticação para serem consumidos. + +![locust](/images/drgarcia1986/locust_session_success.png) + +### Escalando os testes +O Locust é baseado em eventos, graças a isso é possível simular milhares de usuários concorrentes na mesma máquinas, porém em alguns casos esse numero não é o suficiente. Pensando nessa necessidade, o Locust possibilita trabalhar de forma distribuida, através do conceito de **Master** e **Slave**. + +> Segundo a documentação do Locust, recomenda-se instalar a biblioteca [ZeroMQ](http://zeromq.github.io/pyzmq/index.html) para melhorar o desempenho dos testes distribuidos. Essa á razão do _warning_ no momento da execução. + +Para iniciar uma instância _master_ do Locust, basta utilizar o parâmetro ``--master``. + +```bash +(venv)user@machine:~/locust$ locust -f locust_script.py -H http://127.0.0.1:5000 --master +``` +Essa instancia do Locust não irá simular nenhum cliente para teste, apenas irá disponibilizar a interface web com as estatisticas dos testes realizados e irá aguardar a conexão dos _slaves_, poís esses serão os responsáveis pela realização dos testes. + +Agora, para iniciar uma instância _slave_ do Locust, são utilizados dois parâmetros, o ``--slave`` que determina que essa instância é um slave e o parâmetro ``--master-host`` com a localização do _master_. + +```bash +(venv)user@machine:~/locust$ locust -f locust_script.py --slave --master-host=192.168.0.15 +``` +> Tanto a máquina **master** quanto as máquinas **slave** precisam ter o Locust instalado e possuir uma cópia do script de testes que será executado de forma distribuida. + +Com as instâncias slaves iniciadas, basta acessar no browser o Locust (da máquina _master_) e ver os testes em ação. + +![locust](/images/drgarcia1986/locust_distributed.png) + +**Referências**
+[Site Oficial](http://locust.io/)
+[Documentação](http://docs.locust.io/en/latest/index.html) diff --git a/content/tutorial-django-17.rst b/content/tutorial-django-17.rst new file mode 100644 index 000000000..3ccc95f1e --- /dev/null +++ b/content/tutorial-django-17.rst @@ -0,0 +1,708 @@ +Tutorial Django 1.7 +=================== + +:date: 2015-01-07 06:00 +:tags: Python, Django +:category: Python, Django +:slug: tutorial-django-17 +:author: Regis da Silva +:email: regis.santos.100@gmail.com +:github: rg3915 +:summary: Veja como criar um projeto usando Django 1.7 baseado em `Intro to Django `_ no site oficial `Django project `_. + +Fiz um video com um tutorial do Django 1.7 seguindo o modelo que tem no site oficial `Django project `_. + +.. youtube:: OayIF9Pz7rE + +Mas... apesar de já existir os posts `Como criar um site com formulário e lista em 30 minutos? `_ e `Seu primeiro projeto Django com Sublime Text no Linux `_ eu resolvi dar uma *atualizada*. E para acrescentar algumas novidades, neste post, eu falo também sobre **shell** e **json**. + +Git Hub: https://github.com/rg3915/django1.7 + +`O que você precisa?`_ + +`Criando o ambiente`_ + +`Instalando Django 1.7 + django-bootstrap3`_ + +`Criando o projeto e a App`_ + +`Editando settings.py`_ + +`Editando models.py`_ + +`Editando urls.py`_ + +`Editando views.py`_ + +`Comandos básicos do manage.py`_ + +`shell`_ + +`Criando os templates`_ + +`forms.py`_ + +`admin.py`_ + +`Carregando dados de um json`_ + +`Video e GitHub`_ + +O que você precisa? +------------------- + +* Instale primeiro o `pip `_ + +Primeira opção + +.. code-block:: shell + + $ wget https://bootstrap.pypa.io/get-pip.py + $ sudo python get-pip.py + +Segunda opção + +.. code-block:: shell + + $ sudo apt-get install -y python-pip + +* `VirtualEnv `_ + +.. code-block:: shell + + $ sudo pip install virtualenv + $ # ou + $ sudo apt-get install -y virtualenv + +Criando o ambiente +------------------ + +Vamos criar um ambiente usando o **Python 3**, então digite + +.. code-block:: shell + + $ virtualenv -p /usr/bin/python3 django1.7 + +onde ``django1.7`` é o nome do ambiente. + +Entre na pasta + +.. code-block:: shell + + $ cd django1.7 + +e *ative o ambiente* + +.. code-block:: shell + + $ source bin/activate + +Para diminuir o caminho do prompt digite + +.. code-block:: shell + + $ PS1="(`basename \"$VIRTUAL_ENV\"`):/\W$ " + +Instalando Django 1.7 + django-bootstrap3 +----------------------------------------- + +.. code-block:: shell + + $ pip install django==1.7.2 django-bootstrap3 + +*Dica*: se você digitar ``pip freeze`` você verá a versão dos programas instalados. + +Criando o projeto e a App +------------------------- + +Para criar o projeto digite + +.. code-block:: shell + + $ django-admin.py startproject mysite . + +repare no ponto no final do comando, isto permite que o arquivo ``manage.py`` fique nesta mesma pasta *django1.7*. + +Agora vamos criar a *app* **bands**, mas vamos deixar esta *app* dentro da pasta *mysite*. Então entre na pasta + +.. code-block:: shell + + $ cd mysite + +e digite + +.. code-block:: shell + + $ python ../manage.py startapp bands + +A intenção é que os arquivos tenham a seguinte hierarquia nas pastas: + + +.. code-block:: bash + + . + ├── fixtures.json + ├── manage.py + ├── mysite + │   ├── bands + │   │   ├── admin.py + │   │   ├── forms.py + │   │   ├── models.py + │   │   ├── templates + │   │   │   ├── bands + │   │   │   │   ├── band_contact.html + │   │   │   │   ├── band_detail.html + │   │   │   │   ├── band_form.html + │   │   │   │   ├── band_listing.html + │   │   │   │   ├── member_form.html + │   │   │   │   └── protected.html + │   │   │   ├── base.html + │   │   │   ├── home.html + │   │   │   └── menu.html + │   │   ├── tests.py + │   │   └── views.py + │   ├── settings.py + │   ├── urls.py + + +Agora permaneça sempre na pasta *django1.7* + +.. code-block:: shell + + $ cd .. + +e digite + +.. code-block:: shell + + $ python manage.py migrate + +para criar a primeira *migração* e + +.. code-block:: shell + + $ python manage.py runserver + +e veja que o projeto já está funcionando. + + +Editando settings.py +-------------------- + +Em ``INSTALLED_APPS`` acrescente as linhas abaixo. + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'mysite.bands', + 'bootstrap3', + ) + +E mude também o idioma. + +.. code-block:: python + + LANGUAGE_CODE = 'pt-br' + +Editando models.py +------------------ + +.. code-block:: python + + from django.db import models + + + class Band(models.Model): + + """A model of a rock band.""" + name = models.CharField(max_length=200) + can_rock = models.BooleanField(default=True) + + class Meta: + ordering = ['name'] + verbose_name = 'band' + verbose_name_plural = 'bands' + + def __str__(self): + return self.name + + # count members by band + # conta os membros por banda + def get_members_count(self): + return self.band.count() + + # retorna a url no formato /bands/1 + def get_band_detail_url(http://github.com/self): + return u"/bands/%i" % self.id + + + class Member(models.Model): + + """A model of a rock band member.""" + name = models.CharField("Member's name", max_length=200) + instrument = models.CharField(choices=( + ('g', "Guitar"), + ('b', "Bass"), + ('d', "Drums"), + ('v', "Vocal"), + ('p', "Piano"), + ), + max_length=1 + ) + + band = models.ForeignKey("Band", related_name='band') + + class Meta: + ordering = ['name'] + verbose_name = 'member' + verbose_name_plural = 'members' + + def __str__(self): + return self.name + + +Editando urls.py +---------------- + +.. code-block:: python + + from django.conf.urls import patterns, include, url + from mysite.bands.views import * + + from django.contrib import admin + + urlpatterns = patterns( + 'mysite.bands.views', + url(http://github.com/r'%5E'),%20'home',%20name='home'), + url(http://github.com/r'%5Ebands/'),%20'band_listing',%20name='bands'), + url(http://github.com/r'^bands/(?P%3Cpk%3E\d+)/$', 'band_detail', name='band_detail'), + url(http://github.com/r'%5Ebandform/),%20BandForm.as_view(), name='band_form'), + url(http://github.com/r'%5Ememberform/),%20MemberForm.as_view(), name='member_form'), + url(http://github.com/r'%5Econtact/'),%20'band_contact',%20name='contact'), + url(http://github.com/r'%5Eprotected/'),%20'protected_view',%20name='protected'), + url(http://github.com/r'%5Eaccounts/login/'),%20'message'), + url(http://github.com/r'%5Eadmin/',%20include(admin.site.urls), name='admin'), + ) + + +Editando views.py +----------------- + +.. code-block:: python + + from django.shortcuts import render + from django.http import HttpResponse + from django.contrib.auth.decorators import login_required + from django.views.generic import CreateView + from django.core.urlresolvers import reverse_lazy + from .models import Band, Member + from .forms import BandContactForm + +A função a seguir retorna um *HttpResponse*, ou seja, uma mensagem simples no navegador. + +.. code-block:: python + + def home(request): + return HttpResponse('Welcome to the site!') + +A próxima função (use uma ou outra) renderiza um *template*, uma página html no navegador. + +.. code-block:: python + + def home(request): + return render(request, 'home.html') + +A função ``band_listing`` retorna todas as bandas. + +Para fazer a *busca* por nome de banda usamos o comando ``var_get_search = request.GET.get('search_box')``, onde ``search_box`` é o nome do campo no template *band_listing.html*. + +E os nomes são retornados a partir do comando ``bands = bands.filter(name__icontains=var_get_search)``. Onde ``icontains`` procura um texto que contém a palavra, ou seja, você pode digitar o nome incompleto. + +.. code-block:: python + + def band_listing(request): + """ A view of all bands. """ + bands = Band.objects.all() + var_get_search = request.GET.get('search_box') + if var_get_search is not None: + bands = bands.filter(name__icontains=var_get_search) + return render(request, 'bands/band_listing.html', {'bands': bands}) + + +A função ``band_contact`` mostra como tratar um formulário na *view*. + +.. code-block:: python + + def band_contact(request): + """ A example of form """ + if request.method == 'POST': + form = BandContactForm(request.POST) + else: + form = BandContactForm() + return render(request, 'bands/band_contact.html', {'form': form}) + +A função ``band_detail`` retorna todos os membros de cada banda, usando o ``pk`` da banda junto com o comando ``filter`` em members. + +.. code-block:: python + + def band_detail(request, pk): + """ A view of all members by bands. """ + band = Band.objects.get(pk=pk) + members = Member.objects.all().filter(band=band) + context = {'members': members, 'band': band} + return render(request, 'bands/band_detail.html', context) + + +``BandForm`` e ``MemberForm`` usam o `Class Based View `_ para tratar formulário de uma forma mais simplificada usando a classe ``CreateView``. O ``reverse_lazy`` serve para tratar a url de retorno de página. + +.. code-block:: python + + class BandForm(CreateView): + template_name = 'bands/band_form.html' + model = Band + success_url = reverse_lazy('bands') + + + class MemberForm(CreateView): + template_name = 'bands/member_form.html' + model = Member + success_url = reverse_lazy('bands') + + +A próxima função requer que você entre numa página somente quando estiver logado. + +`@login_required `_ é um *decorator*. + +``login_url='/accounts/login/'`` é página de erro, ou seja, quando o usuário não conseguiu logar. + +E ``render(request, 'bands/protected.html',...`` é página de sucesso. + +.. code-block:: python + + @login_required(login_url='/accounts/login/') + def protected_view(request): + """ A view that can only be accessed by logged-in users """ + return render(request, 'bands/protected.html', {'current_user': request.user}) + +``HttpResponse`` retorna uma mensagem simples no navegador sem a necessidade de um template. + +.. code-block:: python + + def message(request): + """ Message if is not authenticated. Simple view! """ + return HttpResponse('Access denied!') + + +Comandos básicos do manage.py +----------------------------- + +Para criar novas migrações com base nas alterações feitas nos seus modelos + +.. code-block:: shell + + $ python manage.py makemigrations bands + +Para aplicar as migrações, bem como anular e listar seu status + +.. code-block:: shell + + $ python manage.py migrate + +Para criar um usuário e senha para o admin + +.. code-block:: shell + + $ python manage.py createsuperuser + +Para rodar a aplicação + +.. code-block:: shell + + $ python manage.py runserver + + +shell +----- + +É o *interpretador interativo do python* rodando *via terminal* direto na aplicação do django. + +.. code-block:: shell + + $ python manage.py shell + +Veja a seguir como inserir dados direto pelo *shell*. + +.. code-block:: python + + >>> from mysite.bands.models import Band, Member + >>> # criando o objeto e salvando + >>> band = Band.objects.create(name='Metallica') + >>> band.name + >>> band.can_rock + >>> band.id + >>> # criando uma instancia da banda a partir do id + >>> b = Band.objects.get(id=band.id) + >>> # criando uma instancia do Membro e associando o id da banda a ela + >>> m = Member(name='James Hetfield', instrument='b', band=b) + >>> m.name + >>> # retornando o instrumento + >>> m.instrument + >>> m.get_instrument_display() + >>> m.band + >>> # salvando + >>> m.save() + >>> # listando todas as bandas + >>> Band.objects.all() + >>> # listando todos os membros + >>> Member.objects.all() + >>> # criando mais uma banda + >>> band = Band.objects.create(name='The Beatles') + >>> band = Band.objects.get(name='The Beatles') + >>> band.id + >>> b = Band.objects.get(id=band.id) + >>> # criando mais um membro + >>> m = Member(name='John Lennon', instrument='v', band=b) + >>> m.save() + >>> # listando tudo novamente + >>> Band.objects.all() + >>> Member.objects.all() + >>> exit() + +Criando os templates +-------------------- + +.. code-block:: shell + + $ mkdir mysite/bands/templates + $ mkdir mysite/bands/templates/bands + $ touch mysite/bands/templates/{menu.html,base.html,home.html} + $ touch mysite/bands/templates/bands/{band_listing.html,band_contact.html,protected.html} + +Veja o código de cada template no `github `_. + + Vou comentar apenas os comandos que se destacam. + +**menu.html** + +Repare que no **menu** usamos *links nomeados*, exemplo: + +.. code-block:: html + + Home + +onde 'home' é o nome que foi dado ao link lá em urls.py. + +.. code-block:: python + + url(http://github.com/r'%5E'),%20'home',%20name='home'), + +**base.html** + +Note que em **base** carregamos o `bootstrap3 `_ com o comando + +.. code-block:: python + + {% load bootstrap3 %} + {% bootstrap_css %} + {% bootstrap_javascript %} + +E também o comando **include** para inserir o *menu* em *base*. + +.. code-block:: python + + {% include "menu.html" %} + +**home.html** + +Em **home** usamos o comando **extends** para usar a estrutura inicial do *base*. + +.. code-block:: html + + {% extends "base.html" %} + + +.. image:: images/regisdasilva/home.png + :alt: home.png + + +**band_listing.html** + +Neste template usamos um campo de busca com o nome ``name="search_box"``. Este nome será usada na *views* para localizar um nome de banda. + +E para lista as bandas usamos o comando + +.. code-block:: html + + ... + {% for band in bands %} +
    +
  • +

    {{ band.name}}

    +
  • +
+ {% endfor %} + ... + +.. image:: images/regisdasilva/band_listing.png + :alt: band_listing.png + + + + +**band_contact** + +Usando o *django-bootstrap3* podemos digitar apenas + +.. code-block:: html + + {% load bootstrap3 %} +
+ ... + {% bootstrap_form form %} + ... +
+ +que todos os campos do formulário serão inseridos automaticamente. + +.. image:: images/regisdasilva/form.png + :alt: form.png + + + +**band_detail.html** + +Um destaque para o *if* que "pinta" de azul os membros que são vocal. + +.. code-block:: html + + ... + {% if member.instrument == 'v' %} +
  • + {% else %} +
  • + {% endif %} + ... + +E ``{{ member.get_instrument_display }}`` que retorna o segundo elemento da lista ``choices`` em *models.py*. Ou seja, retorna 'vocal' ao invés de 'v'. + +.. image:: images/regisdasilva/band_detail.png + :alt: band_detail.png + + +**band_form.html** + +.. code-block:: python + + {% extends "base.html" %} + + {% load bootstrap3 %} + + {% block title %} + Band Form + {% endblock title %} + + {% block content %} + +

    Create band

    +
    + {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
    + + {% endblock content %} + + +.. image:: images/regisdasilva/band_form.png + :alt: band_form.png + + +**member_form.html** + +Pegue o código no GitHub. + +.. image:: images/regisdasilva/member_form.png + :alt: member_form.png + + + + +forms.py +-------- + +.. code-block:: shell + + $ touch mysite/bands/forms.py + +Edite o forms.py. + +.. code-block:: python + + from django import forms + from .models import Band, Member + + + class BandContactForm(forms.Form): + subject = forms.CharField(max_length=100) + message = forms.CharField(widget=forms.Textarea) + sender = forms.EmailField() + cc_myself = forms.BooleanField(required=False) + + + class BandForm(forms.ModelForm): + + class Meta: + model = Band + + + class MemberForm(forms.ModelForm): + + class Meta: + model = Member + + +admin.py +-------- + +Criamos uma customização para o admin onde em *members* aparece um **filtro** por *bandas*. + +.. code-block:: python + + from django.contrib import admin + from .models import Band, Member + + + class MemberAdmin(admin.ModelAdmin): + + """Customize the look of the auto-generated admin for the Member model""" + list_display = ('name', 'instrument') + list_filter = ('band',) + + admin.site.register(Band) # Use the default options + admin.site.register(Member, MemberAdmin) # Use the customized options + + +.. image:: images/regisdasilva/admin.png + :alt: admin.png + + + +Carregando dados de um json +--------------------------- + +Baixe o arquivo *fixtures.json* no github e no terminal digite + +.. code-block:: python + + $ python manage.py loaddata fixtures.json + + +Video e GitHub +-------------- + +Video: https://www.youtube.com/watch?v=OayIF9Pz7rE + +Git Hub: https://github.com/rg3915/django1.7 \ No newline at end of file diff --git a/content/tutorial-django-2.2.md b/content/tutorial-django-2.2.md new file mode 100644 index 000000000..e293f23ae --- /dev/null +++ b/content/tutorial-django-2.2.md @@ -0,0 +1,683 @@ +title: Tutorial Django 2.2 +Slug: tutorial-django-2 +Date: 2019-06-24 22:00 +Tags: Python, Django +Author: Regis da Silva +Email: regis.santos.100@gmail.com +Github: rg3915 +Twitter: rg3915 +Category: Python, Django + + +Este tutorial é baseado no **Intro to Django** que fica na parte de baixo da página [start](https://www.djangoproject.com/start/) do Django project. + +Até a data deste post o Django está na versão 2.2.2, e requer Python 3. + + +## O que você precisa? + +Python 3.6 ou superior, pip e virtualenv. + +Considere que você tenha instalado Python 3.6 ou superior, [pip](https://pip.readthedocs.io/en/latest/) e [virtualenv](https://virtualenv.pypa.io/en/latest/). + + +## Criando o ambiente + +Crie uma pasta com o nome `django2-pythonclub` + +``` +$ mkdir django2-pythonclub +$ cd django2-pythonclub +``` + +A partir de agora vamos considerar esta como a nossa pasta principal. + +Considerando que você está usando **Python 3**, digite + +``` +python3 -m venv .venv +``` + +Lembre-se de colocar esta pasta no seu `.gitignore`, caso esteja usando. + +``` +echo .venv >> .gitignore +``` + +Depois ative o ambiente digitando + +``` +source .venv/bin/activate +``` + +> Lembre-se, sempre quando você for mexer no projeto, tenha certeza de ter ativado o `virtualenv`, executando o comando `source .venv/bin/activate`. Você deve repetir esse comando toda a vez que você abrir um novo terminal. + +## Instalando Django 2.2.2 + + +Basta digitar + +``` +pip install django==2.2.2 +``` + +Dica: se você digitar `pip freeze` você verá a versão dos programas instalados. + +É recomendável que você atualize a versão do `pip` + +``` +pip install -U pip +``` + +Se der erro então faça: + +``` +python -m pip install --upgrade pip +``` + + + +## Instalando mais dependências + +Eu gosto de usar o [django-extensions](https://django-extensions.readthedocs.io/en/latest/) e o [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks), então digite + +``` +pip install django-extensions django-widget-tweaks python-decouple +``` + +**Importante:** você precisa criar um arquivo `requirements.txt` para instalações futuras do projeto em outro lugar. + +``` +pip freeze > requirements.txt +``` + +Este é o resultado do meu até o dia deste post: + +``` +(.venv):$ cat requirements.txt + +django-extensions==2.1.6 +django-widget-tweaks==1.4.3 +python-decouple==3.1 +pytz==2018.9 +six==1.12.0 +``` + +## Escondendo a SECRET_KEY e trabalhando com variáveis de ambiente + +É muito importante que você não deixe sua SECRET_KEY exposta. Então remova-o imediatamente do seu settings.py ANTES mesmo do primeiro commit. Espero que você esteja usando Git. + +Vamos usar o [python-decouple](https://github.com/henriquebastos/python-decouple) escrito por [Henrique Bastos](https://henriquebastos.net/) para gerenciar nossas variáveis de ambiente. Repare que já instalamos ele logo acima. + +Em seguida você vai precisar criar um arquivo `.env`, para isso rode o comando a seguir, ele vai criar uma pasta contrib e dentro dele colocar um arquivo `env_gen.py` + +``` +if [ ! -d contrib ]; then mkdir contrib; fi; git clone https://gist.github.com/22626de522f5c045bc63acdb8fe67b24.git contrib/ +rm -rf contrib/.git/ # remova a pasta .git que está dentro de contrib. +``` + +Em seguida rode + +``` +python contrib/env_gen.py +``` + +que ele vai criar o arquivo `.env`. + +Supondo que você está versionando seu código com Git, é importante que você escreva isso dentro do seu arquivo `.gitignore`, faça direto pelo terminal + +``` +echo .env >> .gitignore +echo .venv >> .gitignore +echo '*.sqlite3' >> .gitignore +``` + +Pronto, agora você pode dar o primeiro commit. + + +## Criando o projeto e a App + +Para criar o projeto digite + +``` +$ django-admin startproject myproject . +``` + +repare no ponto no final do comando, isto permite que o arquivo `manage.py` fique nesta mesma pasta _django2-pythonclub_ . + +Agora vamos criar a _app_ **bands**, mas vamos deixar esta _app_ dentro da pasta _myproject_. Então entre na pasta + +``` +$ cd myproject +``` + +e digite + +``` +$ python ../manage.py startapp bands +``` + +A intenção é que os arquivos tenham a seguinte hierarquia nas pastas: + +``` +. +├── manage.py +├── myproject +│   ├── bands +│   │   ├── admin.py +│   │   ├── apps.py +│   │   ├── models.py +│   │   ├── tests.py +│   │   └── views.py +│   ├── settings.py +│   ├── urls.py +│   └── wsgi.py +└── requirements.txt +``` + +Agora permaneça sempre na pasta `django2-pythonclub` + +``` +cd .. +``` + +e digite + +``` +$ python manage.py migrate +``` + +para criar a primeira _migração_ (isto cria o banco de dados SQLite), e depois rode a aplicação com + +``` +$ python manage.py runserver +``` + +e veja que a aplicação já está funcionando. Veja o endereço da url aqui + +``` +Django version 2.2.2, using settings 'myproject.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. +``` + + + +## Editando settings.py + +Em `INSTALLED_APPS` acrescente as linhas abaixo. + +```python +INSTALLED_APPS = ( + ... + 'widget_tweaks', + 'django_extensions', + 'myproject.bands', +) +``` + +E mude também o idioma. + +`LANGUAGE_CODE = 'pt-br'` + +E caso você queira o mesmo horário de Brasília-BR + +`TIME_ZONE = 'America/Sao_Paulo'` + +Já que falamos do python-decouple, precisamos de mais alguns ajustes + +```python +from decouple import config, Csv + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = config('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = config('DEBUG', default=False, cast=bool) + +ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) +``` + +Veja que é importante manter sua SECRET_KEY bem guardada (em outro lugar). + +Então crie um arquivo `.env` e guarde sua SECRET_KEY dentro dele, exemplo: + +``` +SECRET_KEY=your_secret_key +DEBUG=True +ALLOWED_HOSTS=127.0.0.1,.localhost +``` + + +## Editando models.py + + +```python +from django.db import models +from django.urls import reverse_lazy + + +class Band(models.Model): + + """A model of a rock band.""" + name = models.CharField(max_length=200) + can_rock = models.BooleanField(default=True) + + class Meta: + ordering = ('name',) + verbose_name = 'band' + verbose_name_plural = 'bands' + + def __str__(self): + return self.name + + def get_absolute_url(http://github.com/self): + # retorna a url no formato /bands/1/ + return reverse_lazy('band_detail', kwargs={'pk': self.pk}) + + def get_members_count(self): + # count members by band + # conta os membros por banda + return self.band.count() + + +class Member(models.Model): + + """A model of a rock band member.""" + name = models.CharField("Member's name", max_length=200) + instrument = models.CharField(choices=( + ('g', "Guitar"), + ('b', "Bass"), + ('d', "Drums"), + ('v', "Vocal"), + ('p', "Piano"), + ), + max_length=1 + ) + + band = models.ForeignKey("Band", related_name='band', on_delete=models.CASCADE) + + class Meta: + ordering = ('name',) + verbose_name = 'member' + verbose_name_plural = 'members' + + def __str__(self): + return self.name +``` + +Tem algumas coisas que eu não estou explicando aqui para o tutorial ficar curto, mas uma coisa importante é que, como nós editamos o models.py vamos precisar criar um arquivo de migração do novo modelo. Para isso digite + +``` +python manage.py makemigrations +python manage.py migrate +``` + +O primeiro comando cria o arquivo de migração e o segundo o executa, criando as tabelas no banco de dados. + + +## Editando urls.py + + +```python +from django.urls import include, path +from myproject.bands import views as v +from django.contrib import admin + +app_name = 'bands' + +urlpatterns = [ + path('', v.home, name='home'), + # path('bands/', v.band_list, name='bands'), + # path('bands//', v.band_detail, name='band_detail'), + # path('bandform/', v.BandCreate.as_view(), name='band_form'), + # path('memberform/', v.MemberCreate.as_view(), name='member_form'), + # path('contact/', v.band_contact, name='contact'), + # path('protected/', v.protected_view, name='protected'), + # path('accounts/login/', v.message), + path('admin/', admin.site.urls), +] +``` + +Obs: deixei as demais urls comentada porque precisa da função em views.py para que cada url funcione. Descomente cada url somente depois que você tiver definido a função em classe em views.py a seguir. + + +## Editando views.py + +```python +from django.shortcuts import render +from django.http import HttpResponse +from django.contrib.auth.decorators import login_required +from django.views.generic import CreateView +from django.urls import reverse_lazy +from .models import Band, Member +# from .forms import BandContactForm, BandForm, MemberForm +``` + +Obs: Deixei a última linha comentada porque ainda não chegamos em forms. + +A função a seguir retorna um __HttpResponse__, ou seja, uma mensagem simples no navegador. + +```python +def home(request): + return HttpResponse('Welcome to the site!') +``` +A próxima função (use uma ou outra) renderiza um template, uma página html no navegador. + +```python +def home(request): + return render(request, 'home.html') +``` + +A função `band_list` retorna todas as bandas. + +Para fazer a __busca__ por nome de banda usamos o comando `search = request.GET.get('search_box')`, onde `search_box` é o nome do campo no template __band_list.html__. + +E os nomes são retornados a partir do comando `bands = bands.filter(name__icontains=search)`. Onde `icontains` procura um texto que contém a palavra, ou seja, você pode digitar o nome incompleto (ignora maiúsculo ou minúsculo). + +```python +def band_list(request): + """ A view of all bands. """ + bands = Band.objects.all() + search = request.GET.get('search_box') + if search: + bands = bands.filter(name__icontains=search) + return render(request, 'bands/band_list.html', {'bands': bands}) +``` + +Em urls.py pode descomentar a linha a seguir: + +```python +path('bands/', v.band_list, name='bands'), +``` + +A função `band_contact` mostra como tratar um formulário na view. Esta função requer `BandContactForm`, explicado em forms.py. + +```python +def band_contact(request): + """ A example of form """ + if request.method == 'POST': + form = BandContactForm(request.POST) + else: + form = BandContactForm() + return render(request, 'bands/band_contact.html', {'form': form}) +``` + +Em urls.py pode descomentar a linha a seguir: + +```python +path('contact/', v.band_contact, name='contact'), +``` + +A função `band_detail` retorna todos os membros de cada banda, usando o `pk` da banda junto com o comando `filter` em members. + +```python +def band_detail(request, pk): + """ A view of all members by bands. """ + band = Band.objects.get(pk=pk) + members = Member.objects.all().filter(band=band) + context = {'members': members, 'band': band} + return render(request, 'bands/band_detail.html', context) +``` + +Em urls.py pode descomentar a linha a seguir: + +```python +path('bands//', v.band_detail, name='band_detail'), +``` + +`BandCreate` e `MemberCreate` usam o [Class Based View](https://docs.djangoproject.com/en/2.2/ref/class-based-views/) para tratar formulário de uma forma mais simplificada usando a classe `CreateView`. O `reverse_lazy` serve para tratar a url de retorno de página. + +As classes a seguir requerem `BandForm` e `MemberForm`, explicado em forms.py. + +```python +class BandCreate(CreateView): + model = Band + form_class = BandForm + template_name = 'bands/band_form.html' + success_url = reverse_lazy('bands') + + +class MemberCreate(CreateView): + model = Member + form_class = MemberForm + template_name = 'bands/member_form.html' + success_url = reverse_lazy('bands') +``` + +Em urls.py pode descomentar a linha a seguir: + +```python +path('bandform/', v.BandCreate.as_view(), name='band_form'), +path('memberform/', v.MemberCreate.as_view(), name='member_form'), +``` + + +A próxima função requer que você entre numa página somente quando estiver logado. + +`[@login_required](https://docs.djangoproject.com/en/2.2/topics/auth/default/#the-login-required-decorator)` é um __decorator__. + +`login_url='/accounts/login/'` é página de erro, ou seja, quando o usuário não conseguiu logar. + +E `render(request, 'bands/protected.html',...` é página de sucesso. + +```python +@login_required(login_url='/accounts/login/') +def protected_view(request): + """ A view that can only be accessed by logged-in users """ + return render(request, 'bands/protected.html', {'current_user': request.user}) +``` + +`HttpResponse` retorna uma mensagem simples no navegador sem a necessidade de um template. + +```python +def message(request): + """ Message if is not authenticated. Simple view! """ + return HttpResponse('Access denied!') +``` + +Em urls.py pode descomentar a linha a seguir: + +```python +path('protected/', v.protected_view, name='protected'), +path('accounts/login/', v.message), +``` + + +## Comandos básicos do manage.py + + +Para criar novas migrações com base nas alterações feitas nos seus modelos + +`$ python manage.py makemigrations bands` + +Obs: talvez dê erro porque está faltando coisas de forms.py, explicado mais abaixo. + + +Para aplicar as migrações + +`$ python manage.py migrate` + + +Para criar um usuário e senha para o admin + +`$ python manage.py createsuperuser` + +Para rodar a aplicação localmente + +`$ python manage.py runserver` + +Após criar um super usuário você pode entrar em localhost:8000/admin + +Obs: Se você entrar agora em localhost:8000 vai faltar o template home.html. Explicado mais abaixo. + + +## shell_plus + +É o __interpretador interativo do python__ rodando __via terminal__ direto na aplicação do django. + +Com o comando a seguir abrimos o shell do Django. + +`$ python manage.py shell` + +Mas se você está usando o django-extensions (mostrei como configurá-lo no settings.py), então basta digitar + +`$ python manage.py shell_plus` + + +Veja a seguir como inserir dados direto pelo shell. + +```python +>>> from myproject.bands.models import Band, Member +>>> # Com django-extensions não precisa fazer o import +>>> # criando o objeto e salvando +>>> band = Band.objects.create(name='Metallica') +>>> band.name +>>> band.can_rock +>>> band.id +>>> # criando uma instancia da banda a partir do id +>>> b = Band.objects.get(id=band.id) +>>> # criando uma instancia do Membro e associando o id da banda a ela +>>> m = Member(name='James Hetfield', instrument='b', band=b) +>>> m.name +>>> # retornando o instrumento +>>> m.instrument +>>> m.get_instrument_display() +>>> m.band +>>> # salvando +>>> m.save() +>>> # listando todas as bandas +>>> Band.objects.all() +>>> # listando todos os membros +>>> Member.objects.all() +>>> # criando mais uma banda +>>> band = Band.objects.create(name='The Beatles') +>>> band = Band.objects.get(name='The Beatles') +>>> band.id +>>> b = Band.objects.get(id=band.id) +>>> # criando mais um membro +>>> m = Member(name='John Lennon', instrument='v', band=b) +>>> m.save() +>>> # listando tudo novamente +>>> Band.objects.all() +>>> Member.objects.all() +>>> exit() +``` + + + + +## Criando os templates + +Você pode criar os templates com os comandos a seguir... + +``` +$ mkdir -p myproject/bands/templates/bands +$ touch myproject/bands/templates/{menu,base,home}.html +$ touch myproject/bands/templates/bands/{band_list,band_detail,band_form,band_contact,member_form,protected}.html +``` + +... ou pegar os templates já prontos direto do Github. + +``` +mkdir -p myproject/bands/templates/bands +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/base.html -P myproject/bands/templates/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/home.html -P myproject/bands/templates/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/menu.html -P myproject/bands/templates/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_contact.html -P myproject/bands/templates/bands/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_detail.html -P myproject/bands/templates/bands/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_form.html -P myproject/bands/templates/bands/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_list.html -P myproject/bands/templates/bands/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/member_form.html -P myproject/bands/templates/bands/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/protected.html -P myproject/bands/templates/bands/ +``` + + + + +## forms.py + +`$ touch myproject/bands/forms.py` + +Edite o forms.py. + +```python +from django import forms +from .models import Band, Member + + +class BandContactForm(forms.Form): + subject = forms.CharField(max_length=100) + message = forms.CharField(widget=forms.Textarea) + sender = forms.EmailField() + cc_myself = forms.BooleanField(required=False) + + +class BandForm(forms.ModelForm): + + class Meta: + model = Band + fields = '__all__' + + +class MemberForm(forms.ModelForm): + + class Meta: + model = Member + fields = '__all__' +``` + +Lembra que eu deixei o código comentado em views.py? + +Descomente ele por favor + +```python +from .forms import BandContactForm, BandForm, MemberForm +``` + + +## admin.py + +Criamos uma customização para o admin onde em members aparece um filtro por bandas. + +```python +from django.contrib import admin +from .models import Band, Member + + +class MemberAdmin(admin.ModelAdmin): + """Customize the look of the auto-generated admin for the Member model.""" + list_display = ('name', 'instrument') + list_filter = ('band',) + + +admin.site.register(Band) # Use the default options +admin.site.register(Member, MemberAdmin) # Use the customized options +``` + + +## Carregando dados de um CSV + +Vamos baixar alguns arquivos para criar os dados no banco a partir de um CSV. + +``` +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/create_data.py +mkdir fix +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/bands.csv -P fix/ +wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/members.csv -P fix/ +``` + +Estando na pasta principal, rode o comando + +``` +python create_data.py +``` + +que ele vai carregar alguns dados pra você. + +Veja o código de [create_data.py](https://github.com/rg3915/django2-pythonclub/blob/master/create_data.py). + +Veja o código completo em https://github.com/rg3915/django2-pythonclub + +`git clone https://github.com/rg3915/django2-pythonclub.git` + diff --git a/content/tutorial-postgresql.rst b/content/tutorial-postgresql.rst new file mode 100644 index 000000000..23b09ea9d --- /dev/null +++ b/content/tutorial-postgresql.rst @@ -0,0 +1,692 @@ +Tutorial PostgreSql - parte 1 +============================= + +:date: 2015-02-15 12:00 +:tags: python, postresql, banco de dados +:category: Python, Banco de dados +:slug: tutorial-postgresql +:author: Regis da Silva +:email: regis.santos.100@gmail.com +:github: rg3915 +:summary: Esta é a parte 1 (de 3) da série de posts sobre PostgreSql... + +Para quem já leu `Two Scoops of Django `_ sabe que o `PyDanny `_ recomenda fortemente o uso do mesmo SGBD tanto em **produção** como em **testes**. Então esqueça `sqlite `_ para testes, use `MySql `_ ou `Oracle `_ ou `PostgreSQL `_ tanto em produção como em testes, e é sobre este último que vamos falar agora. + +.. figure:: /images/regisdasilva/postgresql.png + :alt: postgresql.png + +Então eu resolvi escrever esta série de 3 posts sobre PostgreSQL. Onde os outros 2 são: + +`PostgreSql e Python3 - parte 2 `_ + +`PostgreSql e Django - parte 3 `_ + +Sumário: + +`Instalação`_ + +`Criando um banco de dados`_ + +`Usando o banco de dados`_ + +`Criando as tabelas`_ + +`Inserindo dados`_ + +`Lendo os dados`_ + +`Atualizando`_ + +`Deletando`_ + +`Herança`_ + +`Modificando tabelas`_ + +`Backup`_ + + Ah, talvez isso aqui também seja útil `postgresql cheat sheet `_. + +Instalação +---------- + +*Eu apanhei bastante para instalar até descobrir que meu SO estava zuado... então se você tiver problemas na instalação não me pergunte porque eu não saberei responder. Eu recorri à comunidade... e Google sempre! Dai eu formatei minha máquina instalei um Linux do zero e deu certo... (não sei instalar no Windows)...* + +Bom, vamos lá. No seu **terminal** digite esta sequência: + +.. code-block:: bash + + $ dpkg -l | grep -i postgres + +Este comando é só pra ver se não tem alguma coisa já instalado. + +Agora instale... + +.. code-block:: bash + + $ sudo apt-get install -y python3-dev python3-setuptools postgresql-9.3 postgresql-contrib-9.3 pgadmin3 libpq-dev build-essential binutils g++ + +`pgadmin3 `_ é a versão com interface visual... no `youtube `_ tem vários tutoriais legais. É bem fácil de mexer. + +``binutils g++`` talvez não seja necessário, mas no meu caso precisou. + +No final, se você conseguir ver a versão do programa é porque deu tudo certo. + +.. code-block:: bash + + $ psql -V + psql (PostgreSQL) 9.3.5 + + +Criando um banco de dados +------------------------- + +Usando o **terminal** sempre! + +.. code-block:: bash + + $ sudo su - postgres + +Seu prompt vai ficar assim: + +.. code-block:: bash + + postgres@usuario:~$ + +Criando o banco + +.. code-block:: bash + + $ createdb mydb + +Se quiser deletar o banco + +.. code-block:: bash + + $ dropdb mydb + +Criando um usuário + +.. code-block:: bash + + $ createuser -P myuser + +Acessando o banco + +.. code-block:: bash + + $ psql mydb + +O comando a seguir define direito de acesso ao novo usuário. + +.. code-block:: sql + + $ GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser; + +Para sair do programa **psql** + +.. code-block:: bash + + \q + +Para sair do *root* pressione ``ctrl+d``. + +Usando o banco de dados +----------------------- + +Antes vamos criar 2 arquivos porque nós iremos usá-los mais na frente. + +*person.csv* + +.. code-block:: bash + + $ cat > person.csv << EOF + name,age,city_id + Abel,12,1 + Jose,54,2 + Thiago,15,3 + Veronica,28,1 + EOF + +*basics.sql* + +.. code-block:: sql + + $ cat > basics.sql << EOF + CREATE TABLE cities (id SERIAL PRIMARY KEY, city VARCHAR(50), uf VARCHAR(2)); + INSERT INTO cities (city, uf) VALUES ('São Paulo', 'SP'); + SELECT * FROM cities; + DROP TABLE cities; + EOF + +Agora, vamos abrir o banco de dados *mydb*. + +.. code-block:: bash + + $ psql mydb + psql (9.3.5) + Type "help" for help. + + mydb=> + +Para rodar os comandos que estão no arquivo *basics.sql* digite + +.. code-block:: bash + + mydb=> \i basics.sql + +Resultado: + +.. code-block:: bash + + CREATE TABLE + INSERT 0 1 + id | city | uf + ----+-----------+---- + 1 | São Paulo | SP + (1 row) + + DROP TABLE + +Como você deve ter percebido, criamos uma tabela *cities*, inserimos um registro, lemos o registro e excluimos a tabela. + +Criando as tabelas +------------------ + +Daqui pra frente vou omitir o prompt, assumindo que seja este: + +.. code-block:: bash + + mydb=> + +Considere as tabelas a seguir: + +.. image:: images/regisdasilva/erd.png + :alt: erd.png + + +Então vamos criar as tabelas... + + Ah, talvez isso aqui também seja útil `postgresql cheat sheet `_. + +.. code-block:: sql + + CREATE TABLE cities (id SERIAL PRIMARY KEY, city VARCHAR(50), uf VARCHAR(2)); + CREATE TABLE person ( + id SERIAL PRIMARY KEY, + name VARCHAR(50), + age INT, + city_id INT REFERENCES cities(id), + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + +Alguns comandos: + +``SERIAL`` é o conhecido *auto incremento* numérico. + +``TIMESTAMP WITH TIME ZONE`` data e hora com *time zone*. + +``DEFAULT NOW()`` insere a data e hora atual automaticamente. + +Mais tipos de campos em `Chapter 8. Data Types `_. + +Para ver as tabelas + +.. code-block:: bash + + \dt + +Resultado: + +.. code-block:: bash + + List of relations + Schema | Name | Type | Owner + --------+--------+-------+-------- + public | cities | table | myuser + public | person | table | myuser + (2 rows) + +Para ver o esquema de cada tabela + +.. code-block:: bash + + \d cities + +Resultado: + +.. code-block:: bash + + Table "public.cities" + Column | Type | Modifiers + --------+-----------------------+----------------------------------------------------- + id | integer | not null default nextval('cities_id_seq'::regclass) + city | character varying(50) | + uf | character varying(2) | + Indexes: + "cities_pkey" PRIMARY KEY, btree (id) + Referenced by: + TABLE "person" CONSTRAINT "person_city_id_fkey" FOREIGN KEY (city_id) REFERENCES cities(id) + +Para deletar as tabelas + +.. code-block:: sql + + DROP TABLE cities + DROP TABLE person + +Para definir o *timezone* + +.. code-block:: sql + + SET timezone = 'America/Sao_Paulo'; + +Caso dê erro ao inserir a data tente + +.. code-block:: sql + + SET timezone = 'UTC'; + +Dica: `stackoverflow `_ + +Inserindo dados +--------------- + +Pra quem já manja de SQL... + +.. code-block:: sql + + INSERT INTO cities (city, uf) VALUES ('São Paulo', 'SP'),('Salvador', 'BA'),('Curitiba', 'PR'); + INSERT INTO person (name, age, city_id) VALUES ('Regis', 35, 1); + +Se lembra do arquivo *person.csv* que criamos lá em cima? + +Troque *user* pelo nome do seu usuário! + +.. code-block:: sql + + COPY person (name,age,city_id) FROM '/home/user/person.csv' DELIMITER ',' CSV HEADER; + +**Erro:** Comigo deu o seguinte erro: + +.. code-block:: bash + + ERROR: must be superuser to COPY to or from a file + +Ou seja, você deve entrar como *root*. Saia do programa e entre novamente. + +.. code-block:: bash + + $ sudo su - postgres + $ psql mydb + mydb=# COPY person (name,age,city_id) FROM '/home/user/person.csv' DELIMITER ',' CSV HEADER; + +Repare que o prompt ficou com ``#``, ou seja, você entrou como *root*. + +Lendo os dados +-------------- + +Pra quem não sabe usar ``JOIN``... + +.. code-block:: sql + + SELECT * FROM person ORDER BY name; + SELECT * FROM person INNER JOIN cities ON (person.city_id = cities.id) ORDER BY name; + +Resultado: + +.. code-block:: bash + + id | name | age | city_id | created | id | city | uf + ----+----------+-----+---------+-------------------------------+----+-----------+---- + 2 | Abel | 12 | 1 | 2015-02-04 03:49:01.597185-02 | 1 | São Paulo | SP + 3 | Jose | 54 | 2 | 2015-02-04 03:49:01.597185-02 | 2 | Salvador | BA + 1 | Regis | 35 | 1 | 2015-02-04 03:47:10.63258-02 | 1 | São Paulo | SP + 4 | Thiago | 15 | 3 | 2015-02-04 03:49:01.597185-02 | 3 | Curitiba | PR + 5 | Veronica | 28 | 1 | 2015-02-04 03:49:01.597185-02 | 1 | São Paulo | SP + (5 rows) + +Exemplo de count e inner join +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Um exemplo interessante, e talvez útil, é saber quantas pessoas moram em cada cidade. + +.. code-block:: sql + + SELECT cities.city, COUNT(person.city_id) AS persons + FROM cities INNER JOIN person ON cities.id = person.city_id + GROUP BY cities.city; + +Mais em `2.7. Aggregate Functions `_. + +.. code-block:: bash + + city | persons + -----------+--------- + São Paulo | 3 + Curitiba | 1 + Salvador | 1 + (3 rows) + +E apenas para não esquecer, o operador para *diferente* é + +.. code-block:: sql + + SELECT * FROM person WHERE city_id <> 1; + +Atualizando +----------- + +.. code-block:: sql + + UPDATE person SET name = 'Jose da Silva', age = age - 2 WHERE name = 'Jose'; + +antes: ``SELECT * FROM person WHERE name Like 'Jose';`` + +.. code-block:: bash + + id | name | age | city_id | created + ----+------+-----+---------+------------------------------- + 3 | Jose | 54 | 2 | 2015-02-04 03:49:01.597185-02 + +depois: ``SELECT * FROM person WHERE id=3;`` + +.. code-block:: bash + + id | name | age | city_id | created + ----+---------------+-----+---------+------------------------------- + 3 | Jose da Silva | 52 | 2 | 2015-02-04 03:49:01.597185-02 + +Note que ``age = age - 2`` fez com que a idade diminuisse de 54 para 52. Ou seja, dá pra fazer operações algébricas com ``UPDATE``. + +Deletando +--------- + +.. code-block:: sql + + DELETE FROM person WHERE age < 18; + +Fazendo ``SELECT * FROM person;`` repare que foram excluidos *Abel* e *Thiago*. + +.. code-block:: bash + + id | name | age | city_id | created + ----+---------------+-----+---------+------------------------------- + 1 | Regis | 35 | 1 | 2015-02-04 03:47:10.63258-02 + 5 | Veronica | 28 | 1 | 2015-02-04 03:49:01.597185-02 + 3 | Jose da Silva | 52 | 2 | 2015-02-04 03:49:01.597185-02 + +Mais informações em `Chapter 2. The SQL Language `_. + +Herança +------- + +Considere o banco de dados chamado *vendas*. + +Suponha que você tenha duas tabelas: *person* (pessoa) e *seller* (vendedor). + +.. image:: images/regisdasilva/erd_vendas.png + :alt: erd_vendas.png + +Então façamos: + +.. code-block:: bash + + $ sudo su - postgres + $ createdb vendas + $ psql vendas + +.. code-block:: sql + + CREATE TABLE person ( + id SERIAL PRIMARY KEY, + name TEXT + ); + CREATE TABLE seller ( + id SERIAL PRIMARY KEY, + name TEXT, + commission DECIMAL(6,2) + ); + INSERT INTO person (name) VALUES ('Paulo'); + INSERT INTO seller (name,commission) VALUES ('Roberto',149.99); + +Dai criamos uma VIEW: + +.. code-block:: sql + + CREATE VIEW peoples AS + SELECT name FROM person + UNION + SELECT name FROM seller; + + SELECT * FROM peoples; + +Que retorna: + +.. code-block:: bash + + name + --------- + Paulo + Roberto + (2 rows) + +Lembre-se que 'Paulo' pertence a *person* e 'Roberto' pertence a *seller*. + +Mas esta não é a melhor solução. Usando a herança façamos da seguinte forma: + +.. code-block:: sql + + DROP VIEW peoples; + DROP TABLE person, seller; + + CREATE TABLE person ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) + ); + CREATE TABLE seller ( + commission DECIMAL(6,2) + ) INHERITS (person); + +Fazendo + +.. code-block:: 'bash' + + \d person + Table "public.person" + Column | Type | Modifiers + --------+-----------------------+----------------------------------------------------- + id | integer | not null default nextval('person_id_seq'::regclass) + name | character varying(50) | + Indexes: + "person_pkey" PRIMARY KEY, btree (id) + Number of child tables: 1 (Use \d+ to list them.) + +E + +.. code-block:: bash + + \d seller + Table "public.seller" + Column | Type | Modifiers + ------------+-----------------------+----------------------------------------------------- + id | integer | not null default nextval('person_id_seq'::regclass) + name | character varying(50) | + commission | numeric(6,2) | + Inherits: person + +A diferença é que com menos código criamos as duas tabelas e não precisamos criar VIEW. Mas a tabela *seller* depende da tabela *person*. + +Portanto não conseguimos deletar a tabela *person* sozinha, precisaríamos deletar as duas tabelas de uma vez. + +**Vantagem:** + +* a associação é do tipo *one-to-one* +* o esquema é extensível +* evita duplicação de tabelas com campos semelhantes +* a relação de dependência é do tipo pai e filho +* podemos consultar o modelo pai e o modelo filho + +**Desvantagem:** + +* adiciona sobrecarga substancial, uma vez que cada consulta em uma tabela filho requer um *join* com todas as tabelas pai. + + +Vamos inserir alguns dados. + +.. code-block:: sql + + INSERT INTO person (name) VALUES ('Paulo'),('Fernando'); + INSERT INTO seller (name,commission) VALUES + ('Roberto',149.99), + ('Rubens',85.01); + +Fazendo + +.. code-block:: sql + + SELECT name FROM person; + + name + ---------- + Paulo + Fernando + Roberto + Rubens + (4 rows) + +Obtemos todos os nomes porque na verdade um *seller* também é um *person*. + +Agora vejamos somente os registros de *person*. + +.. code-block:: sql + + SELECT name FROM ONLY person; + + name + ---------- + Paulo + Fernando + (2 rows) + +E somente os registros de *seller*. + +.. code-block:: sql + + SELECT name FROM seller; + + name + --------- + Roberto + Rubens + (2 rows) + +Mais informações em `3.6. Inheritance `_. + +Modificando tabelas +------------------- + +Vejamos agora como inserir um novo campo numa tabela existente e como alterar as propriedades de um outro campo. + +Para inserir um novo campo façamos + +.. code-block:: sql + + ALTER TABLE person ADD COLUMN email VARCHAR(30); + +Para alterar as propriedades de um campo existente façamos + +.. code-block:: sql + + ALTER TABLE person ALTER COLUMN name TYPE VARCHAR(80); + +Antes era ``name VARCHAR(50)``, agora é ``name VARCHAR(80)``. + +Também podemos inserir um campo com um valor padrão já definido. + +.. code-block:: sql + + ALTER TABLE seller ADD COLUMN active BOOLEAN DEFAULT TRUE; + \d seller + Table "public.seller" + Column | Type | Modifiers + ------------+-----------------------+----------------------------------------------------- + id | integer | not null default nextval('person_id_seq'::regclass) + name | character varying(80) | + commission | numeric(6,2) | + active | boolean | default true + Inherits: person + +Façamos SELECT novamente. + +.. code-block:: sql + + SELECT * FROM seller; + id | name | commission | active + ----+---------+------------+-------- + 3 | Roberto | 149.99 | t + 4 | Rubens | 85.01 | t + (2 rows) + +Vamos definir um email para cada pessoa. O comando ``lower`` torna tudo **minúsculo** e ``||`` **concatena** textos. + +.. code-block:: sql + + UPDATE person SET email = lower(name) || '@example.com'; + SELECT * FROM person; + + id | name | email + ----+----------+---------------------- + 1 | Paulo | paulo@example.com + 2 | Fernando | fernando@example.com + 3 | Roberto | roberto@example.com + 4 | Rubens | rubens@example.com + (4 rows) + +Leia `9.4. String Functions and Operators `_ e `ALTER TABLE `_. + +Backup +------ + +.. code-block:: bash + + pg_dump mydb > bkp.dump + # ou + pg_dump -f bkp.dump mydb + +Excluindo o banco + +.. code-block:: bash + + dropdb mydb + +Criando novamente e **recuperando os dados** + +.. code-block:: bash + + createdb mydb; psql mydb < bkp.dump + +Leia `24.1. SQL Dump `_. + + Se você quiser aqui tem o `post resumido `_ , ou seja, somente os comandos. + +Leia também + +`PostgreSql e Python3 - parte 2 `_ + +`PostgreSql e Django - parte 3 `_ + + +Mais alguns links: + +http://www.postgresonline.com/downloads/special_feature/postgresql90_cheatsheet_A4.pdf + +http://www.postgresql.org/docs/9.4/static/tutorial-createdb.html + +http://www.postgresql.org/docs/9.4/static/index.html + +http://www.postgresql.org/docs/9.4/static/tutorial-sql.html + +http://www.postgresql.org/docs/9.4/static/datatype.html + +http://www.postgresql.org/docs/9.1/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT diff --git a/content/upload-de-arquivos-com-socket-e-struct.md b/content/upload-de-arquivos-com-socket-e-struct.md new file mode 100644 index 000000000..cd98048e7 --- /dev/null +++ b/content/upload-de-arquivos-com-socket-e-struct.md @@ -0,0 +1,198 @@ +Title: Upload de Arquivos com Socket e Struct +Slug: upload-de-arquivos-com-socket-e-struct +Date: 2018-05-17 19:24:00 +Category: Network +Tags: python,tutorial,network,struct,socket +Author: Silvio Ap Silva +Email: contato@kanazuchi.com +Github: kanazux +Linkedin: SilvioApSilva +Twitter: @kanazux +Site: http://kanazuchi.com + +Apesar de termos muitas formas de enviarmos arquivos para servidores hoje em dia, como por exemplo o *scp* e *rsync*, podemos usar o python com seus módulos *built-in* para enviar arquivos a servidores usando struct para serializar os dados e socket para criar uma conexão cliente/servidor. + +### *Struct* + +O módulo [struct](https://docs.python.org/3/library/struct.html) é usado para converter bytes no python em formatos do struct em C. +Com ele podemos enviar num único conjunto de dados o nome de um arquivo e os bytes referentes ao seus dados. + +Struct também é utilizado para serializar diversos tipos de dados diferentes, como bytes, inteiros, floats além de outros, no nosso caso usaremos apenas bytes. + +Vamos criar um arquivo para serializar. + +```python +!echo "Upload de arquivos com sockets e struct\nCriando um arquivo para serializar." > arquivo_para_upload +``` + +Agora em posse de um arquivo vamos criar nossa estrutura de bytes para enviar. + +```python +arquivo = "arquivo_para_upload" +``` + +```python +with open(arquivo, 'rb') as arq: + dados_arquivo = arq.read() + serializar = struct.Struct("{}s {}s".format(len(arquivo), len(dados_arquivo))) + dados_upload = serializar.pack(*[arquivo.encode(), dados_arquivo]) +``` + +Por padrão, struct usa caracteres no início da sequência dos dados para definir a ordem dos bytes, tamanho e alinhamento dos bytes nos dados empacotados. +Esses caracteres podem ser vistos na [seção 7.1.2.1](https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment) da documentação. +Como não definimos, será usado o **@** que é o padrão. + +Nessa linha: +```python +serializar = struct.Struct("{}s {}s".format(len(arquivo), len(dados_arquivo))) +``` +definimos que nossa estrutura serializada seria de dois conjuntos de caracteres, a primeira com o tamanho do nome do arquivo, e a segunda com o tamanho total dos dados lidos em +```python +dados_arquivo = arq.read() +``` + +Se desempacotarmos os dados, teremos uma lista com o nome do arquivo e os dados lidos anteriormente. + +```python +serializar.unpack(dados_upload)[0] +``` + + b'arquivo_para_upload' + + +```python +serializar.unpack(dados_upload)[1] +``` + + b'Upload de arquivos com sockets e struct\\nCriando um arquivo para serializar.\n' + + +Agora de posse dos nossos dados já serializados, vamos criar um cliente e um servidor com socket para transferirmos nosso arquivo. + +### *Socket* + +O modulo [socket](https://docs.python.org/3/library/socket.html) prove interfaces de socket BSD, disponiveis em praticamente todos os sistemas operacionais. + +#### Familias de sockets + +Diversas famílias de sockets podem ser usadas para termos acessos a objetos que nos permitam fazer chamadas de sistema. +Mais informações sobre as famílias podem ser encontradas na [seção 18.1.1](https://docs.python.org/3/library/socket.html#socket-families) da documentação. No nosso exemplo usaremos a AF_INET. + +#### AF_INET + +**AF_INET** precisa basicamente de um par de dados, contendo um endereço IPv4 e uma porta para ser instanciada. +Para endereços IPv6 o modulo disponibiliza o **AF_INET6** + +#### Constante [SOCK_STREAM] + +As constantes representam as famílias de sockets, como a constante AF_INET e os protocolos usados como parâmetros para o modulo socket. +Um dos protocolos mais usados encontrados na maioria dos sistemas é o SOCK_STREAM. + +Ele é um protocolo baseado em comunicação que permite que duas partes estabeleçam uma conexão e conversem entre si. + +### *Servidor e cliente socket* + +Como vamos usar um protocolo baseado em comunicação, iremos construir o servidor e cliente paralelamente para um melhor entendimento. + +> Servidor + +Para esse exemplo eu vou usar a porta 6124 para o servidor, ela esta fora da range reservada pela IANA para sistemas conhecidos, que vai de 0-1023. + +Vamos importar a bilioteca socket e definir um host e porta para passarmos como parametro para a constante AF_INET. + +```python +import socket +host = "127.0.0.1" +porta = 6124 +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +``` + +Agora usaremos o metodo bind para criarmos um ponto de conexão para nosso cliente. Esse método espera por uma tupla contento o host e porta como parâmetros. + +```python +sock.bind((host, porta)) +``` + +Agora vamos colocar nosso servidor socket em modo escuta com o metodo listen. Esse método recebe como parâmetro um número inteiro (**backlog**) definindo qual o tamanho da fila que será usada para receber pacotes SYN até dropar a conexão. Usaremos um valor baixo o que evita SYN flood na rede. Mais informações sobre *backlog* podem ser encontradas na [RFC 7413](https://tools.ietf.org/html/rfc7413). + +```python +sock.listen(5) +``` + +Agora vamos colocar o nosso socket em um loop esperando por uma conexão e um início de conversa. Pra isso vamos usar o metodo *accept* que nos devolve uma tupla, onde o primeiro elemento é um novo objeto socket para enviarmos e recebermos informações, e o segundo contendo informações sobre o endereço de origem e porta usada pelo cliente. + +**Vamos criar um diretório para salvar nosso novo arquivo.** + +```python +!mkdir arquivos_recebidos +``` + +*Os dados são enviados sempre em bytes*. **Leia os comentários** + +```python +while True: + novo_sock, cliente = sock.accept() + with novo_sock: # Caso haja uma nova conexão + ouvir = novo_sock.recv(1024) # Colocamos nosso novo objeto socket para ouvir + if ouvir != b"": # Se houver uma mensagem... + """ + Aqui usaremos os dados enviados na mensagem para criar nosso serielizador. + + Com ele criado poderemos desempacotar os dados assim que recebermos. + Veja no cliente mais abaixo qual a primeira mensagem enviada. + """ + mensagem, nome, dados = ouvir.decode().split(":") + serializar = struct.Struct("{}s {}s".format(len(nome.split()[0]), int(dados.split()[0]))) + novo_sock.send("Pode enviar!".encode()) # Enviaremos uma mensagem para o cliente enviar os dados. + dados = novo_sock.recv(1024) # Agora iremos esperar por eles. + nome, arquivo = serializar.unpack(dados) # Vamos desempacotar os dados + """ + Agora enviamos uma mensagem dizendo que o arquivo foi recebido. + + E iremos salva-lo no novo diretório criado. + """ + novo_sock.send("Os dados do arquivo {} foram enviados.".format(nome.decode()).encode()) + with open("arquivos_recebidos/{}".format(nome.decode()), 'wb') as novo_arquivo: + novo_arquivo.write(arquivo) + print("Arquivo {} salvo em arquivos_recebidos.".format(nome.decode())) + + Arquivo arquivo_para_upload salvo em arquivos_recebidos. + +``` + +> Cliente + +Nosso cliente irá usar o metodo *connect* para se conectar no servidor e a partir dai começar enviar e receber mensagens. Ele também recebe como parâmetros uma tupla com o host e porta de conexão do servidor. + +```python +host = '127.0.0.1' +porta = 6124 +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Cria nosso objeto socket +sock.connect((host, porta)) +sock.send("Enviarei um arquivo chamado: {} contendo: {} bytes".format( + arquivo, len(dados_arquivo)).encode()) # Enviamos a mensagem com o nome e tamanho do arquivo. +ouvir = sock.recv(1024) # Aguardamos uma mensagem de confirmação do servidor. +if ouvir.decode() == "Pode enviar!": + sock.send(dados_upload) # Enviamos os dados empacotados. + resposta = sock.recv(1024) # Aguardamos a confirmação de que os dados foram enviados. + print(resposta.decode()) + + Os dados do arquivo arquivo_para_upload foram enviados. + +``` + +Agora podemos checar nossos arquivos e ver se eles foram salvos corretamente. + +```python +!md5sum arquivo_para_upload; md5sum arquivos_recebidos/arquivos_para_upload + + 605e99b3d873df0b91d8834ff292d320 arquivo_para_upload + 605e99b3d873df0b91d8834ff292d320 arquivos_recebidos/arquivo_para_upload + +``` + +Com base nesse exemplo, podem ser enviados diversos arquivos, sendo eles texto, arquivos compactados ou binários. + +Sem mais delongas, fiquem com Cher e até a próxima! + +Abraços. diff --git a/content/upload-de-arquivos-no-django-entendendo-os-modos-de-leitura.md b/content/upload-de-arquivos-no-django-entendendo-os-modos-de-leitura.md new file mode 100644 index 000000000..2b7f226f0 --- /dev/null +++ b/content/upload-de-arquivos-no-django-entendendo-os-modos-de-leitura.md @@ -0,0 +1,104 @@ +Title: Upload de arquivos no Django: entendendo os modos de leitura +Date: 2016-02-26 18:39 +Tags: django, csv, upload, HttpRequest, UploadedFile +Category: Django +Slug: upload-de-arquivos-no-django-entendendo-os-modos-de-leitura +Author: Eduardo Cuducos +About_author: Sociólogo, geek, cozinheiro e fã de esportes. +Email: cuducos@gmail.com +Github: cuducos +Site: http://cuducos.me +Twitter: cuducos +Linkedin: cuducos +Alias: /upload-de-arquivos-no-django-entendo-os-modos-de-leitura + +Em uma conversa com a galera do [Welcome to the Django](http://welcometothedjango.com.br) 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](http://djangoproject.com), atualizando do Python 2 para o Python 3, e me deparei com um pedaço de uma _view_ que, como o [Henrique Bastos](http://henriquebastos.net) falou, funcionava “por acaso” no Python 2: + + +```python +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`](https://docs.djangoproject.com/en/1.9/ref/request-response/#django.http.HttpRequest.FILES) e descobri que os arquivos ali disponíveis são instâncias de [`UploadedFile`](https://docs.djangoproject.com/en/1.9/ref/files/uploads/#django.core.files.uploadedfile.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](https://curl.haxx.se/docs/manpage.html#-F), por exemplo, indicava que os arquivos eram enviados como binários. A documentação da [Requests](http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads) 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](http://ipython.org): + + +```python +import csv +from django.core.files.uploadedfile import UploadedFile +uploaded = UploadedFile(open('teste.txt', 'rb'), 'teste.txt') +``` + +Assim testei o trecho que dava problema… + + +```python +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`: + + +```python +uploaded.close() +uploaded.open('r') +``` + +Agora sim o arquivo está pronto para o `csv.reader(…)`: + + +```python +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 diff --git a/content/what_the_flask_extensoes.md b/content/what_the_flask_extensoes.md new file mode 100644 index 000000000..968aa526d --- /dev/null +++ b/content/what_the_flask_extensoes.md @@ -0,0 +1,898 @@ +Title: What the Flask? pt 4 - Extensões para o Flask +Slug: what-the-flask-pt-4-extensoes-para-o-flask +Date: 2017-04-26 09:00 +Tags: flask,web,tutorial,what-the-flask +Author: Bruno Cezar Rocha +Email: rochacbruno@gmail.com +Github: rochacbruno +Bitbucket: rochacbruno +Site: http://brunorocha.org +Twitter: rochacbruno +Linkedin: rochacbruno +Gittip: rochacbruno +Category: Flask + + +What The Flask - 4/5 +-------------------- + +> Finalmente!!! Depois de uma longa espera o **What The Flask** está de volta! +> A idéia era publicar primeiro a parte 4 (sobre Blueprints) e só depois a 5 +> sobre como criar extensões. Mas esses 2 temas estão muito interligados +> então neste artigo os 2 assuntos serão abordados. E a parte 5 será a final falando sobre deploy! + + +
    +code +
    + + +1. [**Hello Flask**](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python.html): Introdução ao desenvolvimento web com Flask +2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask.html): Estruturando aplicações Flask +3. [**Plug & Use**](/what-the-flask-pt-3-plug-use-extensoes-essenciais-para-iniciar-seu-projeto.html): extensões essenciais para iniciar seu projeto +4. [**Magic(app)**](/what-the-flask-pt-4-extensoes-para-o-flask.html): Criando Extensões para o Flask(**<-- Você está aqui**) +5. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem + + +Não sei se você ainda se lembra? mas estavámos desenvolvendo um [CMS de notícias](http://github.com/rochacbruno/wtf), +utilizamos as extensões `Flask-MongoEngine`, `Flask-Security`, `Flask-Admin` e `Flask-Bootstrap`. + +E neste artigo iremos adicionar mais uma extensão em nosso CMS, mas **iremos criar uma extensão** +ao invés de usar uma das extensões disponíveis. + +> **Extensão ou Plugin?** Por [definição](https://pt.wikipedia.org/wiki/Plug-in) +**plugins** diferem de **extensões**. +Plugins geralmente são externos e utilizam algum tipo de API pública para se integrar com o aplicativo. +Extensões, por outro lado, geralmente são integradas com a lógica da aplicação, isto é, as interfaces do próprio framework. +Ambos, plugins e extensões, aumentam a utilidade da aplicação original, mas **plugin** é algo relacionado apenas a camadas de mais +alto nível da aplicação, enquanto extensões estão acopladas ao framework. Em outras palavras, **plugin** é algo que você escreve +pensando apenas na **sua aplicação** e está altamente acoplado a ela enquanto **extensão** é algo que pode ser usado **por qualquer aplicação** escrita no mesmo framework pois está acoplado a lógica do framework e não das aplicações escritas com ele. + +# Quando criar uma extensão? + +Faz sentido criar uma extensão quando você identifica uma functionalidade +que pode ser **reaproveitada** por outras aplicações Flask, assim você mesmo se +beneficia do fato de não precisar reescrever (copy-paste) aquela funcionalidade +em outros apps e também pode publicar sua extensão como **open-source** beneficiando +toda a **comunidade** e incorporando as **melhorias**, ou seja, **todo mundo ganha!** + +## Exemplo prático + +Imagine que você está publicando seu site mas gostaria de prover um `sitemap`. (URL que lista todas as páginas existentes no seu site usada pelo Google para melhorar a sua classificação nas buscas). + +Como veremos no exemplo abaixo publicar um **sitemap** é uma tarefa bastante +simples, mas é uma coisa que você precisará fazer em todos os sites que +desenvolver e que pode se tornar uma funcionalidade mais complexa na medida +que necessitar controlar datas de publicação e extração de URLs automáticamente. + +## Exemplo 1 - Publicando o sitemap sem o uso de extensões + +```python +from flask import Flask, make_response + +app = Flask(__name__) + +@app.route('/artigos') +def artigos(): + "este endpoint retorna a lista de artigos" + +@app.route('/paginas') +def paginas(): + "este endpoint retorna a lista de paginas" + +@app.route('/contato') +def contato(): + "este endpoint retorna o form de contato" + +###################################### +# Esta parte poderia ser uma extensão +###################################### +@app.route('/sitemap.xml') +def sitemap(): + items = [ + '{0}'.format(page) + for page in ['/artigos', '/paginas', '/contato'] + ] + sitemap_xml = ( + '' + '{0}' + ).format(''.join(items)).strip() + response = make_response(sitemap_xml) + response.headers['Content-Type'] = 'application/xml' + return response +####################################### +# / Esta parte poderia ser uma extensão +####################################### + + +app.run(debug=True) +``` + +Executando e acessando http://localhost:5000/sitemap.xml o resultado será: + +```xml + + + /artigos + /paginas + /contato + +``` + +> **NOTE**: O app acima é apenas um simples exemplo de como gerar um sitemap e a +intenção dele aqui é apenas a de servir de exemplo para extensão que criaremos +nos próximos passos, existem outras boas práticas a serem seguidas na +publicação de [sitemap](https://www.sitemaps.org/pt_BR/protocol.html) mas não é +o foco deste tutorial. + +Vamos então transformar o exemplo acima em uma **Extensão** e utilizar uma abordagem +mais dinâmica para coletar as URLs, mas antes vamos entender como funcionam as +extensões no Flask. + +# Como funciona uma Extensão do Flask? + +Lembra que na [parte 2](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask.html#app_factory) desta série falamos sobre os **patterns** do Flask e sobre +o **application factory** e **blueprints**? As extensões irão seguir estes +mesmos padrões em sua arquitetura. + +O grande **"segredo"** para se trabalhar com **Flask** é entender que sempre iremos +interagir com uma instância geralmente chamada de **app** e que pode ser acessada +também através do proxy **current_app** e que sempre aplicaremos um padrão que +é quase **funcional** neste deste objeto sendo que a grande diferença aqui é +que neste paradigma do Flask as funções (chamadas de **factories**) introduzem **side effects**, ou seja, elas alteram ou injetam funcionalidades no **app** que é manipulado até que chega ao seu **estado de execução**. (enquanto em um paradigma funcional as funções não podem ter side effects) + +Também é importante entender os estados **configuração**, **request** e **interativo/execução** do +Flask, asunto que abordamos na [parte 1](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python.html#o_contexto_da_aplicacao) desta série. + +Em resumo, iremos criar uma **factory** que recebe uma instância da classe **Flask**, o objeto **app** (ou o acessa através do proxy **current_app**) e então altera ou injeta funcionalidades neste objeto. + +Dominar o Flask depende muito do entendimento desse **padrão de factories** e os **3 estados da app** citados acima, se você não está seguro quanto a estes conceitos aconselho reler +as [partes 1 e 2](/tag/what-the-flask.html) desta série (e é claro sinta se livre para deixar comentários +com as suas dúvidas). + +Só para relembrar veja o seguinte exemplo: + +```python +app = Flask(__name__) # criamos a instancia de app +admin = Admin() # instancia do Flask-Admin ainda não inicializada + +do_something(app) # injeta ou altera funcionalidades do app +do_another_thing(app, admin) # injeta ou altera funcionalidades do apo ou do admin +Security(app) # adiciona funcionalidades de login e controle de acesso +Cache(app) # adiciona cache para views e templates +SimpleSitemap(app) # A extensão que iremos criar! Ela adiciona o /sitemap.xml no app + +admin.init_app(app) # agora sim inicializamos o flask-admin no modo lazy +``` + +Olhando o código acima pode parecer bastante simples, você pode achar que basta +receber a intância de `app` e sair alterando sem seguir nenhum padrão. + +```python +def bad_example_of_flask_extension(app): + "Mal exemplo de factory que injeta ou altera funcionalidades do app" + # adiciona rotas + @app.route('/qualquercoisa) + def qualquercoisa(): + ... + # substitui objetos usando composição + app.config_class = MyCustomConfigclass + # altera config + app.config['QUALQUERCOISA'] = 'QUALQUERVALOR' + # sobrescreve métodos e atributos do app + app.make_responde = another_function + # Qualquer coisa que o Python (e a sua consciência) permita! +``` + +Isso pode provavelmente funcionar mas não é uma boa prática, existem muitos +problemas com o **factory** acima e alguns deles são: + +1. Nunca devemos definir rotas dessa maneira com `app.route` em uma extensão **o correto é usar blueprints**. +2. Lembre-se dos **3 estados do Flask**, devemos levar em consideração que no momento +que a aplicação for iniciada a extensão pode ainda não estar pronta para ser carregada, +por exemplo, a sua extensão pode depender de um banco de dados que ainda não +foi inicializado, portanto as extensões precisam sempre ter um **modo lazy**. +3. Usar funções pode se ruma boa idéia na maioria dos casos, mas lembre-se que +precisamos manter estado em algumas situações então pode ser **melhor usar +classes** ao invés de funções pois as classes permitem uma construção mais dinâmica. +4. Nossa extensão precisa ser reutilizavel em qualquer app flask, portanto +devemos usar namespaces ao ler configurações e definir rotas. + +> **NOTE**: Eu **NÃO** estou dizendo que você não deve usar funções para extender seu app +Flask, eu mesmo faço isso em muitos casos. Apenas tenha em mente esses detalhes +citados na hora de decidir qual abordagem usar. + +# Patterns of a Flask extension + +Preferencialmente uma **Extensão** do **Flask** deve seguir esses **padrões**: + +- Estar em um módulo nomeado com o prefixo **flask_** como por exemplo **flask_admin** +e **flask_login** e neste artigo criaremos o **flask_simple_sitemap**. (**NOTE:** Antigamente as extensões usavam o padrão **flask.ext.nome_extensao** mas este tipo de plublicação de módulo com namespace do **flask.ext** foi descontinuado e não é mais recomendado.) +- Fornecer um **método** de inicialização **lazy** nomeado **init_app**. +- Ler todas as suas configurações a partir do **app.config** +- Ter suas configurações prefixadas com o nome da extensão, exemplo: **SIMPLE_SITEMAP_URLS** ao invés de apenas **SITEMAP_URLS** pois isto evita conflitos com configurações de outras extensões. +- Caso a extensão adicione views e URL rules, isto deve ser feito com **Blueprint** +- Caso a extensão adicione arquivos estáticos ou de template isto também deve ser +feito com **Blueprint** +- ao registrar **urls** e **endpoints** permitir que sejam dinâmicos através de config e +sempre prefixar com o nome da extensão. Exemplo: `url_for('simple_sitemap.sitemap')` é +melhor do que `url_for('sitemap')` para evitar conflitos com outras extensões. + +> **NOTE:** Tenha em mente que regras foram feitas para serem **quebradas**, +> O **Guido** escreveu na [PEP8](https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds) "A Foolish Consistency is the Hobgoblin of Little Minds", ou seja, tenha os padrões como guia mas nunca deixe que eles atrapalhem +> o seu objetivo. +> Eu mesmo já quebrei essa regra 1 no [flasgger](http://github.com/rochacbruno/flasgger), +> eu poderia ter chamado de `flask_openapi` ou `flask_swaggerui` mas achei `Flasgger` um nome mais divertido :) + +De todos os padrões acima o mais importante é o de evitar o conflito com outras extensões! + +> Zen do Python: **Namespaces são uma ótima idéia! vamos usar mais deles!** + +# Criando a extensão Simple Sitemap + +Ok, agora que você já sabe a **teoria** vamos colocar em **prática**, +abre ai o **vim**, **emacs**, **pycharm** ou seu **vscode** e vamos reescrever o +nosso app do **Exemplo 1** usando uma extensão chamada **flask_simple_sitemap** e para isso vamos começar criando a extensão: + +A extensão será um **novo módulo Python** que vai ser instalado usando `setup` ou `pip` portanto crie um projeto separado. + + +Em seu terminal `*nix` execute os comandos: + +```bash +➤ $ +# Entre na pasta onde vc armazena seus projetos +cd Projects + +# crie o diretório root do projeto +mkdir simple_sitemap +cd simple_sitemap + +# torna a extensão instalável (vamos escrever esse arquivo depois) +touch setup.py + +# é sempre bom incluir uma documentação básica +echo '# Prometo documentar essa extensão!' > README.md + +# se não tiver testes não serve para nada! :) +touch tests.py + +# crie o diretório que será o nosso package +mkdir flask_simple_sitemap + +# __init__.py para transformar o diretório em Python package +echo 'from .base import SimpleSitemap' > flask_simple_sitemap/__init__.py + +# A implementação da extensão será escrita neste arquivo +# (evite usar main.py pois este nome é reservado para .zipped packages) +touch flask_simple_sitemap/base.py + +# Crie a pasta de templates +mkdir flask_simple_sitemap/templates + +# usaremos Jinja para gerar o XML +touch flask_simple_sitemap/templates/sitemap.xml + +# incluindo templates no build manifest do setuptools +echo 'recursive-include flask_simple_sitemap/templates *' > MANIFEST.in + +# precisaremos de um arquivo de requirements para os testes +touch requirements-test.txt + +``` + +Agora voce terá a seguinte estrutura: + +```bash +➤ tree +simple_sitemap/ +├── flask_simple_sitemap/ +│   ├── base.py +│   ├── __init__.py +│   └── templates/ +│   └── sitemap.xml +├── MANIFEST.in +├── README.md +├── requirements-test.txt +├── setup.py +└── tests.py + +2 directories, 8 files +``` + +O primeiro passo é escrever o `setup.py` já que a extensão precisa ser instalavél: + + +```python +from setuptools import setup, find_packages + +setup( + name='flask_simple_sitemap', + version='0.0.1', + packages=find_packages(), + include_package_data=True, + zip_safe=False +) +``` + +Eu tenho o costumo de praticar o que eu chamo de **RDD** (**R**eadme **D**riven **D**development), ou seja, +ao criar projetos como este eu costumo escreve primeiro o **README.md** explicando como deve funcionar e só +depois de ter isto pronto que eu começo a programar. + +Edite o `README.md` + +```text + +# Flask Simple Sitemap + +Esta extensão adiciona a funcionalidade de geração de sitemap ao seu app flask. + +## Como instalar? + +Para instalar basta clonar o repositório e executar: + + $ python setup.py install + +Ou via `pip install flask_simple_sitemap` + +## Como usar? + +Basta importar e inicializar: + + from flask import Flask + from flask_simple_sitemap import SimpleSitemap + + app = Flask(__name__) + SimpleSitemap(app) + + @app.route('/) + def index(): + return 'Hello World' + +Como em toda extensão Flask também é possível inicializar no modo Lazy chamando +o método `init_app` + +## Opções de configuração: + +esta extensão utiliza o namespace de configuração `SIMPLE_SITEMAP_` + +- **SIMPLE_SITEMAP_BLUEPRINT** define o nome do blueprint e do url prefix (default: `'simple_sitemap'`) +- **SIMPLE_SITEMAP_URL** define a url que irá renderizar o sitemap (default: `'/sitemap.xml'`) +- **SIMPLE_SITEMAP_PATHS** dicionário de URLs a serem adicionadas ao sitemap (exemplo: URLs criadas a partir de posts em bancos de dados) + +``` + +Agora que já sabemos pelo **README** o que queremos entregar de funcionalidade +já é possível escrever o **tests.py** e aplicar também um pouco de **TDD** + +O Flask tem uma integração bem interesante com o **py.test** e podemos editar o **tests.py** +da seguinte maneira: + +> **NOTE:** O ideal é fazer o **test setup** no arquivo **conftest.py** e usar fixtures do py.test, mas aqui vamos +> escrever tudo junto no **tests.py** para ficar mais prático. +
    +> **Zen do Python:** `praticidade vence a pureza` :) + + +```python +#################################################################### +# Início do Test Setup +# + +import xmltodict +from flask import Flask +from flask_simple_sitemap import SimpleSitemap + +app = Flask(__name__) +extension = SimpleSitemap() + +app.config['SIMPLE_SITEMAP_BLUEPRINT'] = 'test_sitemap' +app.config['SIMPLE_SITEMAP_URL'] = '/test_sitemap.xml' +app.config['SIMPLE_SITEMAP_PATHS'] = { + '/this_is_a_test': {'lastmod': '2017-04-24'} +} + +@app.route('/hello') +def hello(): + return 'Hello' + +# assert lazy initialization +extension.init_app(app) + +client = app.test_client() + +# +# Final Test Setup +#################################################################### + +#################################################################### +# Cláusula que Permite testar manualmente o app com `python tests.py` +# +if __name__ == '__main__': + app.run(debug=True) +# +# acesse localhost:5000/test_sitemap.xml +#################################################################### + +#################################################################### +# Agora sim os testes que serão executados com `py.test tests.py -v` +# + +def test_sitemap_uses_custom_url(): + response = client.get('/test_sitemap.xml') + assert response.status_code == 200 + +def test_generated_sitemap_xml_is_valid(): + response = client.get('/test_sitemap.xml') + xml = response.data.decode('utf-8') + result = xmltodict.parse(xml) + assert 'urlset' in result + # rules are Ordered + assert result['urlset']['url'][0]['loc'] == '/test_sitemap.xml' + assert result['urlset']['url'][1]['loc'] == '/hello' + assert result['urlset']['url'][2]['loc'] == '/this_is_a_test' + +# +# Ao terminar o tutorial reescreva esses testes usando fixtures :) +# E é claro poderá adicionar mais testes! +################################################################### +``` + +Para que os testes acima sejam executados precisamos instalar algumas dependencias portanto o **requirements-test.txt** precisa deste conteúdo: + +``` +flask +pytest +xmltodict +--editable . +``` + +> **NOTE:** ao usar `--editable .` no arquivo de requirements você faz com que a extensão seja auto instalada em modo de edição +> desta forma executamos apenas `pip install -r requirements-test.txt` e o pip se encarrega de rodar o `python setup.py develop`. + +Vamos então começar a desenvolver editando o **front-end** da extensão +que será escrito no template: **flask_simple_sitemap/templates/sitemap.xml** este template espera um dict `paths` +chaveado pela `location` e contento `sitemap tag names` em seu valor. +exemplo `paths = {'/artigos': {'lastmod': '2017-04-24'}, ...}` + +```xml + + + {% for loc, data in paths.items() %} + + {{loc|safe}} + {% for tag_name, value in data.items() %} + <{{tag_name}}>{{value}} + {% endfor %} + + {% endfor %} + +``` + +E então finalmente escreveremos a classe base da extensão no arquivo **flask_simple_sitemap/base.py** + +Lembrando de algumas boas práticas: + +- Prover um método de inicialização **lazy** com a assinatura `init_app(self, app)` +- Impedir registro em **duplicidade** e inserir um registro no `app.extensions` +- Ao adicionar rotas sempre usar **Blueprints** e **namespaces** (o Blueprint já se encarrega do namespace nas urls) +- Configs devem sempre ter um prefixo, faremos isso com o `get_namespace` que aprendemos na parte 1 + +> **NOTE:** Leia atentamente os **comentários** e **docstrings** do código abaixo. + +```python +# coding: utf-8 +from flask import Blueprint, render_template, make_response + + +class SimpleSitemap(object): + "Extensão Flask para publicação de sitemap" + + def __init__(self, app=None): + """Define valores padrão para a extensão + e caso o `app` seja informado efetua a inicialização imeditatamente + caso o `app` não seja passado então + a inicialização deverá ser feita depois (`lazy`) + """ + self.config = { + 'blueprint': 'simple_sitemap', + 'url': '/sitemap.xml', + 'paths': {} + } + self.app = None # indica uma extensão não inicializada + + if app is not None: + self.init_app(app) + + def init_app(self, app): + """Método que Inicializa a extensão e + pode ser chamado de forma `lazy`. + + É interessante que este método seja apenas o `entry point` da extensão + e que todas as operações de inicialização sejam feitas em métodos + auxiliares privados para melhor organização e manutenção do código. + """ + self._register(app) + self._load_config() + self._register_view() + + def _register(self, app): + """De acordo com as boas práticas para extensões devemos checar se + a extensão já foi inicializada e então falhar explicitamente caso + seja verdadeiro. + Se tudo estiver ok, então registramos o app.extensions e o self.app + """ + if not hasattr(app, 'extensions'): + app.extensions = {} + + if 'simple_sitemap' in app.extensions: + raise RuntimeError("Flask extension already initialized") + + # se tudo está ok! então registramos a extensão no app.extensions! + app.extensions['simple_sitemap'] = self + + # Marcamos esta extensão como inicializada + self.app = app + + def _load_config(self): + """Carrega todas as variaveis de config que tenham o prefixo `SIMPLE_SITEMAP_` + Por exemplo, se no config estiver especificado: + + SIMPLE_SITEMAP_URL = '/sitemap.xml' + + Podemos acessar dentro da extensão da seguinte maneira: + + self.config['url'] + + e isto é possível por causa do `get_namespace` do Flask utilizado abaixo. + """ + self.config.update( + self.app.config.get_namespace( + namespace='SIMPLE_SITEMAP_', + lowercase=True, + trim_namespace=True + ) + ) + + def _register_view(self): + """aqui registramos o blueprint contendo a rota `/sitemap.xml`""" + self.blueprint = Blueprint( + # O nome do blueprint deve ser unico + # usaremos o valor informado em `SIMPLE_SITEMAP_BLUEPRINT` + self.config['blueprint'], + + # Agora passamos o nome do módulo Python que o Blueprint + # está localizado, o Flask usa isso para carregar os templates + __name__, + + # informamos que a pasta de templates será a `templates` + # já é a pasta default do Flask mas como a nossa extensão está + # adicionando um arquivo na árvore de templates será necessário + # informar + template_folder='templates' + ) + + # inserimos a rota atráves do método `add_url_rule` pois fica + # esteticamente mais bonito do que usar @self.blueprint.route() + self.blueprint.add_url_rule( + self.config['url'], # /sitemap.xml é o default + endpoint='sitemap', + view_func=self.sitemap_view, # usamos outro método como view + methods=['GET'] + ) + + # agora só falta registar o blueprint na app + self.app.register_blueprint(self.blueprint) + + @property + def paths(self): + """Cria a lista de URLs que será adicionada ao sitemap. + + Esta property será executada apenas quando a URL `/sitemap.xml` for requisitada + + É interessante ter este método seja público pois permite que seja sobrescrito + e é neste método que vamos misturar as URLs especificadas no config com + as urls extraidas do roteamento do Flask (Werkzeug URL Rules). + + Para carregar URLs dinâmicamente (de bancos de dados) o usuário da extensão + poderá sobrescrever este método ou contribur com o `SIMPLE_SITEMAP_PATHS` + + Como não queremos que exista duplicação de URLs usamos um dict onde + a chave é a url e o valor é um dicionário completando os dados ex: + + app.config['SIMPLE_SITEMAP_PATHS'] = { + '/artigos': { + 'lastmod': '2017-01-01' + }, + ... + } + """ + + paths = {} + + # 1) Primeiro extraimos todas as URLs registradas na app + for rule in self.app.url_map.iter_rules(): + # Adicionamos apenas GET que não receba argumentos + if 'GET' in rule.methods and len(rule.arguments) == 0: + # para urls que não contém `lastmod` inicializamos com + # um dicionário vazio + paths[rule.rule] = {} + + # caso existam URLs que recebam argumentos então deverão ser carregadas + # de forma dinâmica pelo usuário da extensão + # faremos isso na hora de usar essa extensão no CMS de notícias. + + # 2) Agora carregamos URLs informadas na config + # isso é fácil pois já temos o config carregado no _load_config + paths.update(self.config['paths']) + + # 3) Precisamos sempre retornar o `paths` neste método pois isso permite + # que ele seja sobrescrito com o uso de super(....) + return paths + + def sitemap_view(self): + "Esta é a view exposta pela url `/sitemap.xml`" + # geramos o XML através da renderização do template `sitemap.xml` + sitemap_xml = render_template('sitemap.xml', paths=self.paths) + response = make_response(sitemap_xml) + response.headers['Content-Type'] = 'application/xml' + return response +``` + +> **NOTE**: Neste exemplo usamos um método de desenvolvimento muito legal que eu chamo de: +>
    **ITF** (**Important Things First**) onde **Arquitetura, Documentação, Testes e Front End (e protótipos)** são muito mais importantes do que a implementação de back end em si. +>
    Assumimos que caso a nossa implementação seja alterada os conceitos anteriores se mantém integros com a proposta do produto. +>
    Ordem de prioridade no projeto: **1)** Definimos a arquitetura **2)** Escrevemos documentação **3)** Escrevemos testes **4)** Implementamos front end (e protótipo) **5)** **back end é o menos importante** do ponto de vista do produto e por isso ficou para o final! :) + +O código da extensão etá disponível em [http://github.com/rochacbruno/flask_simple_sitemap](http://github.com/rochacbruno/flask_simple_sitemap) + +# Usando a extensão em nosso CMS de notícias + +Agora vem a melhor parte, usar a extensão recém criada em nosso projeto existente. + +O repositório do CMS está no [github](https://github.com/rochacbruno/wtf/tree/extended) +Precisamos do **MongoDB** em execução e a forma mais fácil é através do **docker** + +```bash +➤ docker run -d -p 27017:27017 mongo +``` + +Se preferir utilize uma instância do MongoDB instalada localmente ou um Mongo As a Service. + +> **NOTE:** O modo de execução acima é efemero e não persiste os dados, para persistir use `-v $PWD/etc/mongodata:/data/db`. + +Agora que o Mongo está rodando execute o nosso CMS. + +Obtendo, instalando e executando: + +```bash +➤ +git clone -b extended --single-branch https://github.com/rochacbruno/wtf.git extended +cd wtf + +# adicione nossa extensao nos requirements do CMS +# sim eu publiquei no PyPI, mas se preferir instale a partir do fonte que vc escreveu +echo 'flask_simple_sitemap' >> requirements.txt + +# activate a virtualenv +pip install -r requirements.txt + +# execute +python run.py +``` + +Agora com o CMS executando acesse [http://localhost:5000](http://localhost:5000) e verá a seguinte tela: + +
    +cms +
    + +Os detalhes dessa aplicação você deve ser lembrar pois estão nas partes [1, 2 e 3](http://pythonclub.com.br/tag/what-the-flask.html) deste tutorial. + +Agora você pode +se registrar novas notícias usando o link [cadastro](http://localhost:5000/noticias/cadastro) e precisará efetuar **login** e para isso +deve se [registrar](http://localhost:5000/admin/register/) como usuário do aplicativo. + +### Temos as seguintes **urls** publicads no CMS + +- `'/'` lista todas as noticias na home page +- `'/noticias/cadastro'` exibe um formulário para incluir noticias +- `'/noticia/` acessa uma noticia especifica +- `'/admin'` instancia do **Flask Admin** para adminstrar usuários e o banco de dados + +Agora vamos incluir a extensão **flask_simple_sitemap** que criamos e adicionar as URLs das noticias dinâmicamente. + +Edite o arquico **wtf/news_app.py** incluindo a extensão **flask_simple_sitemap** e também adicionando as URLs de todas as noticias +que existirem no banco de dados. + +```python +# coding: utf-8 +from os import path +from flask import Flask +from flask_bootstrap import Bootstrap +from flask_security import Security, MongoEngineUserDatastore +from flask_debugtoolbar import DebugToolbarExtension + +############################################### +# 1) importe a nossa nova extensão +from flask_simple_sitemap import SimpleSitemap + +from .admin import configure_admin +from .blueprints.noticias import noticias_blueprint +from .db import db +from .security_models import User, Role +from .cache import cache + +############################################## +# 2) importe o model de Noticia +from .models import Noticia + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + db.init_app(app) + Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role)) + configure_admin(app) + DebugToolbarExtension(app) + cache.init_app(app) + + ############################################ + # 3) Adicionane as noticias ao sitemap + app.config['SIMPLE_SITEMAP_PATHS'] = { + '/noticia/{0}'.format(noticia.id): {} # dict vazio mesmo por enquanto! + for noticia in Noticia.objects.all() + } + + ############################################ + # 4) Inicialize a extensão SimpleSitemap + sitemap = SimpleSitemap(app) + + return app +``` + +Agora execute o `python run.py` e acesse [http://localhost:5000/sitemap.xml](http://localhost:5000/sitemap.xml) + +Você verá o **sitemap** gerado incluindo as URLs das notícias cadastradas! + +```xml + + + /noticia/58ffe998e138231eef84f9a7 + + + /noticias/cadastro + + + / + +... +# + um monte de URL do /admin aqui + +``` + +> **NOTE:** Funcionou! legal! porém ainda não está bom. Existem algumas +> melhorias a serem feitas e vou deixar essas melhorias para você fazer! + +# Desafio What The Flask! + +## 1) Melhore a geração de URLs do CMS + +Você reparou que a URL das notícias está bem feia? +**/noticia/58ffe998e138231eef84f9a7** não é uma boa URL +Para ficar mais simples no começo optamos por usar o **id** da notícia como URL +mas isso não é uma boa prática e o pior é que isso introduz até mesmo **problemas de +segurança**. + +Você conseguer arrumar isso? transformando em: **/noticia/titulo-da-noticia-aqui** ? + +Vou dar umas dicas: + +**Altere o Model:** + +- Altere o model Noticia em: [https://github.com/rochacbruno/wtf/blob/extended/wtf/models.py#L5](https://github.com/rochacbruno/wtf/blob/extended/wtf/models.py#L5). +- Insira um novo campo para armazenar o **slug** da notícia, o valor será o título transformado para **lowercase**, **espaços substituidos por -**. Ex: para o **título**: 'Isto é uma notícia' será salvo o **slug**: 'isto-e-uma-noticia'. +- Utilize o método **save** do MongoEngine ou se preferir use **signals** para gerar o **slug** da notícia. +- Utilize o módulo **awesome-slugify** disponível no `PyPI` para criar o **slug** a partir do título. + +**Altere a view:** + +- Altere a view que está em: [https://github.com/rochacbruno/wtf/blob/extended/wtf/blueprints/noticias.py#L45](https://github.com/rochacbruno/wtf/blob/extended/wtf/blueprints/noticias.py#L45). +- Ao invés de `` utilize ``. +- Efetue a busca através do campo **slug**. + +Altere as urls passadas ao **SIMPLE_SITEMAP_PATHS** usando o **slug** ao invés do **id**. + +## 2) Adicione data de publicação nas notícias + +Reparou que o sitemap está sem a data da notícia? adicione o campo **modified** +ao model **Noticia** e faça com que ele salve a data de **criação** e/ou **alteração** da notícia. + +**Queremos algo como:** + +```python + app.config['SIMPLE_SITEMAP_PATHS'] = { + '/noticia/{0}'.format(noticia.slug): { + 'lastmod': noticia.modified.strftime('%Y-%m-%d') + } + for noticia in Noticia.objects.all() + } +``` + +**Para gerar no sitemap:** + +```xml + + + /noticia/titulo-da-noticia + 2017-04-25 + +... +``` + +## 3) Crie uma opção de filtros na extensão `simple_sitemap` + +Uma coisa chata também é o fato do `sitemap.xml` ter sido gerado com esse +monte de URL indesejada. As URLs iniciadas com `/admin` por exemplo não precisam +ir para o `sitemap.xml`. + +Implemente esta funcionalidade a extensão: + +> **DICA**: Use **regex** `import re` + +```python +app.config['SIMPLE_SITEMAP_EXCLUDE'] = [ + # urls que derem match com estes filtros não serão adicionadas ao sitemap + '^/admin/.*' +] +``` + +> **DESAFIO:** Após implementar as melhorias inclua nos comentários um link para a sua solução, pode ser um **fork** dos repositórios +> ou até mesmo um link para **gist** ou **pastebin** (enviarei uns adesivos de Flask para quem completar o desafio!) + +
    + +> O diff com as alterações realizadas no CMS encontra-se no [github.com/rochacbruno/wtf](https://github.com/rochacbruno/wtf/compare/extended...sitemap?expand=1)
    +> A versão final da extensão `SimpleSitemap` está no [github.com/rochacbruno/flask_simple_sitemap](https://github.com/rochacbruno/flask_simple_sitemap)
    +> A versão final do CMS app está no [github.com/rochacbruno/wtf](https://github.com/rochacbruno/wtf/tree/sitemap) + +Se você está a procura de uma extensão para `sitemap` para uso em produção aconselho a [flask_sitemap](https://github.com/inveniosoftware/flask-sitemap) + +
    + +> **END:** Sim chegamos ao fim desta quarta parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas partes iremos efetuar o deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada. + +
    + +> **PUBLICIDADE:** Iniciarei um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente. + +
    + +> **PUBLICIDADE 2:** Também estou escrevendo um livro de receitas **Flask CookBook** através da plataforma LeanPub, caso tenha interesse por favor preenche o formulário na [página do livro](https://leanpub.com/pythoneflask) + +
    + +> **PUBLICIDADE 3:** Inscreva-se no meu novo [canal de tutoriais](http://www.youtube.com/channel/UCKkjiNMtdyCOFE3-w7TB8xw?sub_confirmation=1) + +Muito obrigado e aguardo seu feedback com dúvidas, sugestões, correções etc na caixa de comentários abaixo. + +Abraço! "Python é vida!" + diff --git a/content/what_the_flask_flask_patterns.md b/content/what_the_flask_flask_patterns.md index 807130796..8c547e481 100644 --- a/content/what_the_flask_flask_patterns.md +++ b/content/what_the_flask_flask_patterns.md @@ -14,22 +14,21 @@ Category: Flask -What The Flask - 2/6 +What The Flask - 2/5 ----------- -> **CONTEXT PLEASE:** Esta é a segunda parte da série **What The Flask**, 6 artigos para se tornar um **Flasker** (não, não é um cowboy que carrega sua garrafinha de whisky para todo lado). A primeira parte está aqui no [PythonClub](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python) e o app está no [github](https://github.com/rochacbruno/wtf/tree/pt-1). +> **CONTEXT PLEASE:** Esta é a segunda parte da série **What The Flask**, 5 artigos para se tornar um **Flasker** (não, não é um cowboy que carrega sua garrafinha de whisky para todo lado). A primeira parte está aqui no [PythonClub](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python) e o app está no [github](https://github.com/rochacbruno/wtf/tree/pt-1).
    a flasker
    Professional Flask Developer
    -1. [**Hello Flask**](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python): Introdução ao desenvolvimento web com Flask -2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask): Estruturando aplicações Flask - **<-- Você está aqui** -3. **Plug & Use**: extensões essenciais para iniciar seu projeto -4. **DRY**: Criando aplicativos reusáveis com Blueprints -5. **from flask.ext import magic**: Criando extensões para o Flask e para o Jinja2 -6. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem. +1. [**Hello Flask**](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python.html): Introdução ao desenvolvimento web com Flask +2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask.html): Estruturando aplicações Flask(**<-- Você está aqui**) +3. [**Plug & Use**](/what-the-flask-pt-3-plug-use-extensoes-essenciais-para-iniciar-seu-projeto.html): extensões essenciais para iniciar seu projeto +4. [**Magic(app)**](/what-the-flask-pt-4-extensoes-para-o-flask.html): Criando Extensões para o Flask +5. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem
    > **Você sabia?** Flask quer dizer "Frasco/Frasqueira", ou seja, aquela garrafinha ali da foto acima que geralmente os cowboys, os Irlandeses, o John Wayne, os bebados profissionais e os hipsters gostam de utilizar para tomar desde vodka, whisky, vinho e até suco de caju (no caso dos [hipsters](http://www.cafepress.com/+hipster+flasks)). Bom você pode estar se perguntando: Por que colocar esse nome em um framework? Antes do Flask já existia o Bottle "garrafa" que surgiu com a idéia revolucionária de ser um framework de um [arquivo só](https://github.com/defnull/bottle/blob/master/bottle.py). Como o criador do Flask é meio contrário a esta idéia de colocar um monte de código Python em um único arquivo ele decidiu ironizar e fazer uma piada de 1 de abril e então criou um framework chamado [Denied](http://denied.immersedcode.org/) que era uma piada ironizando o Bottle e outros micro frameworks, mas as pessoas levaram a sério e gostaram do [estilo do denied!](http://denied.immersedcode.org/screencast.mp4) A partir disso ele decidiu pegar as boas idéias tanto do Bottle como do Denied e criar algo sério e então surgiu o Flask. O nome vem da idéia de que **Bottle**/Garrafa é para tomar de goladas, mas **Flask**/Frasco você toma **uma gota por vez**, desta forma você aprecia melhor a bebida e até hoje o slogan do Flask é " Development one drop at time". @@ -495,6 +494,9 @@ class BasicTestCase(unittest.TestCase): self.assertEqual(request.args.get('name'), 'BrunoRocha') ``` +> **UPDATE NOTE:** O Flask 0.12.1 inclui o `app.test_client` que é recomendado ao invés do uso de `app.test_request_context`, porém na data da escrita deste artigo +> o Flask ainda estava na versão 0.10.0. Na parte 4 deste tutorial abordamos os testes com **py.test** e **app.test_client** + ### 2. Instanciar multiplos apps em um mesmo projeto ##### /wtf/multiple_run.py @@ -586,6 +588,7 @@ Usar o método **update** em conjunto com a funcionalidade de descompactação d Como já falei no ínicio deste tópico, é muito comum você precisar que as configurações variem de acordo com o ambiente ou servidor em que está rodando, para isso o Flask fornece mais 3 abordagems de configurações bastante úteis. +> **UPDATE NOTE**: Desenvolvi a ferramenta **Dynaconf** que possui integração com o Flask e fornece configurações dinâmicas, veja mais em: [http://github.com/rochacbruno/dynaconf](http://github.com/rochacbruno/dynaconf) #### Usando um arquivo de configurações *.cfg @@ -1068,16 +1071,20 @@ Também temos o **multiple_run** que utiliza o DispatcherMiddleware para juntar Nos próximos capítulos iremos evoluir este app para o uso de algumas extensões essenciais, uncluiremos controle de login, cache, interface de administração, suporte a html e markdown nas noticias e outras coisas. -> **END:** Sim chegamos ao fim desta segunda parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas 4 partes iremos nos aprofundar no uso e desenvolvimento de extensões e blueprints e também questṍes relacionados a deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada. +> **END:** Sim chegamos ao fim desta segunda parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas 3 partes iremos nos aprofundar no uso e desenvolvimento de extensões e blueprints e também questṍes relacionados a deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada.
    -> **PUBLICIDADE:** Estou iniciando um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente. +> **PUBLICIDADE:** Iniciarei um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente.
    > **PUBLICIDADE 2:** Também estou escrevendo um livro de receitas **Flask CookBook** através da plataforma LeanPub, caso tenha interesse por favor preenche o formulário na [página do livro](https://leanpub.com/pythoneflask) +
    + +> **PUBLICIDADE 3:** Inscreva-se no meu novo [canal de tutoriais](http://www.youtube.com/channel/UCKkjiNMtdyCOFE3-w7TB8xw?sub_confirmation=1) + Muito obrigado e aguardo seu feedback com dúvidas, sugestões, correções etc na caixa de comentários abaixo. diff --git a/content/what_the_flask_introducao_ao_desenvolvimento_web_com_python.md b/content/what_the_flask_introducao_ao_desenvolvimento_web_com_python.md index 12b2ed4d2..15f8480a7 100644 --- a/content/what_the_flask_introducao_ao_desenvolvimento_web_com_python.md +++ b/content/what_the_flask_introducao_ao_desenvolvimento_web_com_python.md @@ -14,29 +14,27 @@ Category: Flask -What The Flask - 1/6 +What The Flask - 1/5 ----------- -### 6 passos para ser um Flask ninja! +### 5 passos para ser um Flask ninja! -Nesta série de 6 artigos/tutoriais pretendo abordar de maneira bem detalhada +Nesta série de 5 artigos/tutoriais pretendo abordar de maneira bem detalhada o desenvolvimento web com o framework Flask. Depois de mais de um ano desenvolvendo projetos profissionais com o Flask e -adquirindo experiência também no desenvolvimento do projeto open source -[Quokka CMS](http://quokkaproject.org) resolvi compartilhar algumas dicas +adquirindo experiência também no desenvolvimento de [projetos open source com Flask](http://brunorocha.org/my-projects/) como o [QuokkaCMS](http://quokkaproject.org) resolvi compartilhar algumas dicas para facilitar a vida de quem pretende começar a desenvolver para web com Python. > **TL;DR:** A versão final do aplicativo explicado neste artigo está no [github](https://github.com/rochacbruno/wtf) A série **W**hat **T**he **F**lask será dividida nos seguintes capítulos. -1. [**Hello Flask**](/what_the_flask_introducao_ao_desenvolvimento_web_com_python.html): Introdução ao desenvolvimento web com Flask - **<-- Você está aqui** -2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask): boas práticas na estrutura de aplicações Flask -3. **Plug & Use**: extensões essenciais para iniciar seu projeto -4. **DRY**: Criando aplicativos reusáveis com Blueprints -5. **from flask.ext import magic**: Criando extensões para o Flask e para o Jinja2 -6. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem. +1. [**Hello Flask**](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python.html): Introdução ao desenvolvimento web com Flask(**<-- Você está aqui**) +2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask.html): Estruturando aplicações Flask +3. [**Plug & Use**](/what-the-flask-pt-3-plug-use-extensoes-essenciais-para-iniciar-seu-projeto.html): extensões essenciais para iniciar seu projeto +4. [**Magic(app)**](/what-the-flask-pt-4-extensoes-para-o-flask.html): Criando Extensões para o Flask +5. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem # Hello Flask ### Parte 1 - Introdução ao desenvolvimento web com Flask @@ -917,6 +915,8 @@ def cadastro(): if __name__ == "__main__": app.run(debug=True, use_reloader=True) + # caso tenha problemas com multithreading na hora de inserir o registro no db use + # app.run(debug=False, use_reloader=False) ``` Salve e execute seu aplicativo ``python news_app.py`` e acesse [http://localhost:5000/noticias/cadastro](http://localhost:5000/noticias/cadastro) @@ -1409,6 +1409,8 @@ def media(filename): if __name__ == "__main__": app.run(debug=True, use_reloader=True) + # caso tenha problemas com multithreading na hora de inserir o registro no db use + # app.run(debug=False, use_reloader=False) ``` Após salvar os templates e o news_app.py modificado reinicie o serviço flask no terminal usando CTRL+C e executando novamente ``python news_app.py``. Isso é necessário pois o Flask cria o cache de templates no momento da inicialização do aplicativo. @@ -1418,16 +1420,20 @@ Acesse o app via [localhost:5000](http://localhost:5000) e veja que agora a barr O aplicativo completo pode ser obtido no [repositorio do github](https://github.com/rochacbruno/wtf). -> **END:** Sim chegamos ao fim desta primeira parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas 5 partes iremos nos aprofundar em boas práticas, uso e desenvolvimento de extensões e blueprints e também questṍes relacionados a deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada. +> **END:** Sim chegamos ao fim desta primeira parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas 4 partes iremos nos aprofundar em boas práticas, uso e desenvolvimento de extensões e blueprints e também questṍes relacionados a deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada.
    -> **PUBLICIDADE:** Estou iniciando um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente. +> **PUBLICIDADE:** Iniciarei um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente.
    > **PUBLICIDADE 2:** Também estou escrevendo um livro de receitas **Flask CookBook** através da plataforma LeanPub, caso tenha interesse por favor preenche o formulário na [página do livro](https://leanpub.com/pythoneflask) +
    + +> **PUBLICIDADE 3:** Inscreva-se no meu novo [canal de tutoriais](http://www.youtube.com/channel/UCKkjiNMtdyCOFE3-w7TB8xw?sub_confirmation=1) + Muito obrigado e aguardo seu feedback com dúvidas, sugestões, correções ou bitcoins (LOL) na caixa de comentários abaixo. diff --git a/content/what_the_flask_plug_use.md b/content/what_the_flask_plug_use.md new file mode 100644 index 000000000..6c14d8af2 --- /dev/null +++ b/content/what_the_flask_plug_use.md @@ -0,0 +1,1344 @@ +Title: What the Flask? Pt-3 Plug & Use - extensões essenciais para iniciar seu projeto +Slug: what-the-flask-pt-3-plug-use-extensoes-essenciais-para-iniciar-seu-projeto +Date: 2015-10-21 09:00 +Tags: flask,web,tutorial,what-the-flask +Author: Bruno Cezar Rocha +Email: rochacbruno@gmail.com +Github: rochacbruno +Bitbucket: rochacbruno +Site: http://brunorocha.org +Twitter: rochacbruno +Linkedin: rochacbruno +Gittip: rochacbruno +Category: Flask + + + +What The Flask - 3/5 +----------- + +> Finalmente!!! A terceira parte da série **What The Flask**, mas ainda não acabou, serão 5 artigos para se tornar um **Flasker**, neste capítulo falaremos sobre como instalar e configurar as principais extensões do Flask para torna-lo uma solução full-stack com bootstrap no front-end, ORM para banco de dados, admin parecido com o Django Admin, Cache, Sistema de filas (celery/huey), Controle de Acesso, Envio de email, API REST e Login Social. + +
    +snake +
    Extending Flask
    +
    + +1. [**Hello Flask**](/what-the-flask-pt-1-introducao-ao-desenvolvimento-web-com-python.html): Introdução ao desenvolvimento web com Flask +2. [**Flask patterns**](/what-the-flask-pt-2-flask-patterns-boas-praticas-na-estrutura-de-aplicacoes-flask.html): Estruturando aplicações Flask +3. [**Plug & Use**](/what-the-flask-pt-3-plug-use-extensoes-essenciais-para-iniciar-seu-projeto.html): extensões essenciais para iniciar seu projeto(**<-- Você está aqui**) +4. [**Magic(app)**](/what-the-flask-pt-4-extensoes-para-o-flask.html): Criando Extensões para o Flask +5. **Run Flask Run**: "deploiando" seu app nos principais web servers e na nuvem + +
    + +> **Micro framework?** Bom, o Flask foi criado com a premissa de ser um micro-framework, o que significa que ele não tem a intenção de entregar de bandeja para você todas as coisas que você precisa em único pacotinho e nem comandos mágicos que você roda e instantaneamente tem todo o seu projeto pronto. A idéia do Flask é ser pequeno e te dar o controle de tudo o que acontece no seu aplicativo, mas ao mesmo tempo o Flask se preocupa em ser facilmente extensivel, para isso os desenvolvedores pensaram em padrões que permitem que as extensões sejam instaladas de modo que não haja conflitos (lembra dos BluePrints do capítulo anterior?), além dos BluePrints tem também os patterns para desenvolvimento de extensions que ajuda a tornar a nossa vida mais fácil, nesta parte dessa série vamos instalar e configurar algumas das principais extensões do Flask (todas testadas por mim em projetos reais). + + +# CMS de notícias + +Nesta série estamos desenvolvendo um mini CMS para publicação de notícias, o código está disponível no [github](http://github.com/rochacbruno/wtf) e para cada fase da evolução do projeto tem uma branch diferente. Esse aplicativo de notícias tem os seguintes requisitos: + +- Front-end usando o Bootstrap +- Banco de dados MongoDB +- Controle de acesso para que apenas editores autorizados publiquem notícias +- Interface administrativa para as notícias e usuários +- Cache das notícias para minimizar o acesso ao banco de dados + +> **NOTE:** Existem várias extensões para Flask, algumas são aprovadas pelos desenvolvedores e entram para a lista disponível no site oficial, algumas entram para a listagem do metaflask (projeto em desenvolvimento), e uma grande parte está apenas no github. Como existem várias extensões que fazem a mesma coisa, as vezes é dificil escolher qual delas utilizar, eu irei mostrar aqui apenas as que eu utilizo e que já tenho experiência, mas isso não quer dizer que sejam as melhores, sinta-se a vontade para tentar com outras e incluir sua sugestão nos comentários. + +- [Flask Bootstrap](#bootstrap) - Para deixar as coisas bonitinhas +- [Flask MongoEngine](#mongoengine) - Para armazenar os dados em um banco que é fácil fácil! +- [Flask Security](#flask_security) - Controle de acesso +- [Flask-Admin](#flask_admin) - Um admin tão poderoso quanto o Django Admin +- [Flask Cache](#flask_cache) - Para deixar o MongoDB "de boas" +- Bônus: Utilizaremos a Flask-DebugToolbar + +> **TL;DR:** A versão final do app deste artigo esta no [github](https://github.com/rochacbruno/wtf/tree/extended), os apressados podem querer executar o app e explorar o seu código antes de ler o artigo completo. + +## Deixando as coisas bonitinhas com o Bootstrap! + +Atualmente a versão do nosso CMS está funcional porém bem feinha, não trabalhamos no design das páginas pois obviamente este não é o nosso objetivo, mas mesmo assim podemos deixar as coisas mais bonitinhas. + +
    +wtf_index +
    Atual Layout do nosso CMS
    +
    + +Com a ajuda do Bootstrap e apenas uns ajustes básicos no front end podemos transformar o layout em algo muito mais apresentável. + +Usaremos a extensão Flask-Bootstrap que traz alguns templates de base e utilidades para uso do Bootstrap no Flask. + +Comece editando o arquivo de requirements adicionando **Flask-Bootstrap** + +Arquivo **requirements.txt** +``` +https://github.com/mitsuhiko/flask/tarball/master +dataset +nose +Flask-Bootstrap +``` + + +Agora instale as dependencias em sua virtualenv. + +```bash +pip install -r requirements.txt --upgrade +``` + + +Agora com o Flask-Bootstrap instalado basta iniciarmos a extensão durante a criação de nosso app. + +Editando o arquivo **news_app.py** incluiremos: + +```python +... +from flask_bootstrap import Bootstrap + +def create_app(mode): + ... + ... + Bootstrap(app) + return app +``` + +Sendo que o arquivo completo ficaria: + +```python +# coding: utf-8 +from os import path +from flask import Flask +from .blueprints.noticias import noticias_blueprint +from flask_bootstrap import Bootstrap + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + return app +``` + + +As extensões Flask seguem dois padrões de inicialização: Imediato e Lazy, é recomendado que toda extensão siga este protocolo. + +Inicialização imediata: + +```python +from flask_nome_da_extensao import Extensao +app = Flask(__name__) +Extensao(app) +``` + +Da forma acima sempre importamos uma classe com o nome da extensao e então passamos o nosso **app** como parametro na inicialiação da extensão. Assim durante o init da extensão ela poderá injetar templates, modificar rotas e adicionar configs no app que foi passado como parametro. + +Inicialização Lazy: + +```python +from flask_nome_da_extensao import Extensao +app = Flask(__name__) +extensao = Extensao() # note que não é passado nada como parametro! + + +# em qualquer momento no seu código +Extensao.init_app(app) +``` + +Geralmente o primeiro modo **inicio imediato** é o mais utilizado, o carregamento Lazy é útil em situações mais complexas como por exemplo se o seu sistema estiver esperando a conexão com um banco de dados. + +> NOTE: Toda extensão do Flask deve começar com o nome **flask_** para ser considerada uma extensão dentro dos padrões. + +No nosso caso utilizamos **Bootstrap(app)** e agora o bootstrap já está disponível para ser utilizado em nossos templates! + +### Customizando os templates com o BootStrap. + +Precisaremos efetuar algumas mudanças nos templates para que eles utilizem os estilos do Bootstrap 3.x + +> Não entrarei em detalhes a respeito da estensão Flask-Bootstrap pois temos mais uma série de extensões para instalar, mas você pode consultar a [documentação oficial](https://pythonhosted.org/Flask-Bootstrap/basic-usage.html) para saber mais a respeito dos blocos de template e utilidades disponíveis. + +### Comece alterando o template **base.html** para: + +```html +{%- extends "bootstrap/base.html" %} +{% import "bootstrap/utils.html" as utils %} +{% block title %} {{title or "Notícias"}} {% endblock %} +{% block navbar -%} + +{%- endblock navbar %} + +{% block content %} +
    +
    + {% block news %} + {% endblock %} +
    +{%- endblock content%} +``` + +Algumas coisas continuaram iguais ao template antigo, porém agora estamos utilizando blocos **navbar** e **content** definidos pelo Flask-Bootstrap e criamos um novo bloco **news** que usaremos para mostras as nossas notícias. + +### Altere o template index.html + +```html +{% extends "base.html" %} +{% block news %} +

    Todas as notícias

    + +{% endblock %} + +``` + +apenas mudamos o nome do bloco utilizado de **content** para **news** e adicionamos um título. + +### Altere o template noticia.html + +```html +{% extends "base.html" %} +{% block title%} + {{noticia.titulo}} +{% endblock%} + +{% block news %} +

    {{noticia.titulo}}

    + {% if noticia.imagem %} + + {% endif %} +
    +

    + {{noticia.texto|safe}} +

    +{% endblock %} +``` + +Novamente mudamos o bloco principal de **content** para **news** + +### Altere os templates cadastro.html e cadastro_sucesso.html + +Altere o bloco utilizado de **content** para **news** e o restante deixe como está por enquanto. + +```html +{% extends "base.html" %} +{% block news %} +
    + +
    + +
    + + +
    +{% endblock %} +``` + +### Resultado Final! + +Antes do bootstrap + +
    +wtf_index +
    + +e depois: + +
    +wtf_index_bootstrap +
    + + + +> O diff com as mudanças que foram feitas pode ser acessado neste [link](https://github.com/rochacbruno/wtf/commit/5645de0fb208fc9ee34e502d901c057c9a3f2445) + + +Agora que o app já está com uma cara mais bonita, vamos passar para o próximo requisito: Banco de Dados. + +Até agora usamos o **dataset** que é uma mão na roda! integrando facilmente o nosso projeto com bancos como MySQL, Postgres ou SQLite. + +Porém nosso site de notícias precisa utilizar **MongoDB** e para isso vamos recorrer ao **Flask-MongoEngine** + +## Utilizando MongoDB com Flask + +
    +mongo +
    + +Vamos migrar do Dataset + SQlite para MongoDB e obviamente você irá precisar do MongoDb Server rodando, você pode preferir instalar o Mongo localmente se estiver em uma máquina Linux/Debian: ``sudo apt-get install mongodb`` ou siga instruções no site oficial do Mongo de acordo com seu sistema operacional. + +Uma opção melhor é utilizar o **docker** para executar o MongoDB, desta forma você não precisa instalar o servidor de banco de dados em seu computador, precisa apenas do Docker e da imagem oficial do Mongo. + +Vou mostrar os preocedimentos para instalação e execução no Linux/Debian, mas você pode tranquilamente utilizar outro S.O bastando seguir as instruções encontradas no site do docker. + +#### Instalando o docker + +No linux a maneira mais fácil de instalar o Docker é rodando o seguinte comando + +``` +wget -qO- https://get.docker.com/ | sh +``` + +Se você precisar de ajuda ou estiver usando um sistema operacional alternativo pode seguir as [instruções do site oficial](http://docs.docker.com/linux/started/) + + +#### Executando um container MongoDB + +No DockerHub está disponível a imagem oficial do Mongo, basta executar o comando abaixo para ter o Mongo rodando em um container. + +``` +docker run -d -v $PWD/mongodata:/data/db -p 27017:27017 mongo +``` + +A parte do ``$PWD/mongodata:`` pode ser substituida pelo caminho de sua preferencia, este é o local onde o Mongo irá salvar os dados. + +> Se preferir executar no modo efemero (perdendo todos os dados ao reiniciar o container) execute apenas ``docker run -d -p 27017:27017 mongo`` + + +#### Instalando o MongoDB localmente (não recomendado, use o docker!) + +Você pode preferir não usar o Docker e instalar o Mongo localmente, [baixe o mongo](https://www.mongodb.org/downloads) descompacte, abra um console separado e execute: ``./bin/mongod --dbpath /tmp/`` lembrando de trocar o ``/tmp`` por um diretório onde queira salvar seus dados. + +Se preferir utilize os pacotes oficiais do seu sistema operacional para instalar o Mongo. + +> IMPORTANTE: Para continuar você precisa ter uma instância do MongoDB rodando localmente, no Docker ou até mesmo em um servidor remoto se preferir. + +## Flask-Mongoengine + +Adicione a extensão Flask-Mongoengine ao seu arquivo requirements.txt + +``` +https://github.com/mitsuhiko/flask/tarball/master +flask-mongoengine +nose +Flask-Bootstrap +``` + +Agora execute ``pip install -r requirements.txt --upgrade`` estando na virtualenv de seu projeto. + +### Substituindo o Dataset pelo MongoEngine + +Agora vamos substituir o **dataset** pelo **MongoEngine**, por padrão o MongoEngine tentará conectar no **localhost** na porta **27017** e utilizar o banco de dados **test**. Mas no nosso caso é essencial informarmos exatamente as configurações desejadas. + +No arquivo de configuração em ``development_instance/config.cfg`` adicione as seguintes linhas: + +```python +MONGODB_DB = "noticias" +MONGODB_HOST = "localhost" # substitua se utilizar um server remoto +MONGODB_PORT = 27017 +``` + +Agora vamos ao arquivo ``db.py`` vamos definir a conexão com o banco de dados Mongo, apague todo o conteúdo do arquivo e substitua por: + +**db.py** + +```python +# coding: utf-8 +from flask_mongoengine import MongoEngine +db = MongoEngine() +``` + +Crie um novo arquivo chamado **models.py**, é nesse arquivo que definiremos o esquema de dados nas nossas notícias. Note que o Mongo é um banco schemaless, poderiamos apenas criar um objeto **Noticia(db.DynamicDocument)** usando herança do DynamicDocument e isso tiraria a necessidade da definição do schema, porém, na maioria dos casos definir um schema básico ajuda a construir formulários e validar os dados. + + +**models.py** + +```python +# coding: utf-8 +from .db import db + + +class Noticia(db.Document): + titulo = db.StringField() + texto = db.StringField() + imagem = db.StringField() + +``` + +Nosso próximo passo é alterar as views para que o armazenamento seja feito no MongoDB ao invés do SQLite. + +No MongoEngine algumas operações serão um pouco diferente, alguns exemplos: + +**Criar um novo registro de Noticia** + +```python +Noticia.objects.create(titulo='Hello', texto='World', imagem='caminho/imagem.png') +``` + +**Buscar todas as Noticias** + +```python +Noticia.objects.all() +``` + +**Buscar uma noticia pelo id** +```python +Noticia.objects.get(id='xyz') +``` + + +Altere ``blueprints/noticias.py`` para: + +```python +# coding: utf-8 +import os +from werkzeug import secure_filename +from flask import ( + Blueprint, request, current_app, send_from_directory, render_template +) +from ..models import Noticia + +noticias_blueprint = Blueprint('noticias', __name__) + + +@noticias_blueprint.route("/noticias/cadastro", methods=["GET", "POST"]) +def cadastro(): + if request.method == "POST": + dados_do_formulario = request.form.to_dict() + imagem = request.files.get('imagem') + if imagem: + filename = secure_filename(imagem.filename) + path = os.path.join(current_app.config['MEDIA_ROOT'], filename) + imagem.save(path) + dados_do_formulario['imagem'] = filename + nova_noticia = Noticia.objects.create(**dados_do_formulario) + return render_template('cadastro_sucesso.html', + id_nova_noticia=nova_noticia.id) + return render_template('cadastro.html', title=u"Inserir nova noticia") + + +@noticias_blueprint.route("/") +def index(): + todas_as_noticias = Noticia.objects.all() + return render_template('index.html', + noticias=todas_as_noticias, + title=u"Todas as notícias") + + +@noticias_blueprint.route("/noticia/") +def noticia(noticia_id): + noticia = Noticia.objects.get(id=noticia_id) + return render_template('noticia.html', noticia=noticia) + + +@noticias_blueprint.route('/media/') +def media(filename): + return send_from_directory(current_app.config.get('MEDIA_ROOT'), filename) +``` + +Lembre-se que nós ainda não conectamos ao Mongo Server apenas definimos como será a conexão, então precisaremos agora usar o método lazy de inicialização de extensões chamando o ``init_app()`` do MongoEngine. + +No arquivo ``news_app.py`` adicione as seguintes linhas. + +```python +... +from db import db + +def create_app(mode): + ... + db.init_app(app) + return app +``` + +Sendo que o arquivo final será: + +```python +# coding: utf-8 +from os import path +from flask import Flask +from .blueprints.noticias import noticias_blueprint +from flask_bootstrap import Bootstrap +from db import db + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + db.init_app(app) + return app +``` + +Execute o programa + +```python +python run.py +``` + +E veja se consegue inserir algumas noticias acessando [http://localhost:5000](http://localhost:5000) + +Para explorar os dados do MongoDB visualmente você pode utilizar o RoboMongo. + +
    +wtf_robomongo +
    + +> O Diff das alterações que fizemos relativas ao Flask-MongoEngine podem ser comparadas nos seguintes commits [88effa01b5ffd11f3fd7d5530f90591e421dd109](https://github.com/rochacbruno/wtf/commit/88effa01b5ffd11f3fd7d5530f90591e421dd109) e [189f4d4d2c8af845ccc0b181e4f6a1831578fbfa](https://github.com/rochacbruno/wtf/commit/189f4d4d2c8af845ccc0b181e4f6a1831578fbfa) + +## Controle de acesso com o Flask Security + +
    +security +
    + +Nosso CMS de notícias está inseguro, ou seja, qualquer um que acessar a url [http://localhost:5000/noticias/cadastro](http://localhost:5000/noticias/cadastro) vai conseguir adicionar uma nova notícia sem precisar efetuar login. + +Para resolver este tipo de problema existe a extensão Flask-Login que oferece métodos auxiliares para autenticar usuários e também a Flask-Security é um pacote feito em cima do Flask-Login (controle de autenticação), Flask-Principal (Controle de Permissões) e Flask-Mail (envio de email). + +A vantagem de usar o Flask-Security é que ele já se integra com o MongoEngine e oferece templates prontos para login, alterar senha, envio de email de confirmação etc... + +Começaremos adicionando a dependencia ao arquivo de requirements. + +**requirements.txt** + +``` +https://github.com/mitsuhiko/flask/tarball/master +flask-mongoengine +nose +Flask-Bootstrap +Flask-Security +``` + +E então instalamos com ``pip install -r requirements.txt --upgrade`` + +### Secret Key + +Para encriptar os passwords dos usuários o Flask-Login irá utilizar a chave secret key do settings de seu projeto. É muito importante que esta chave seja segura e gerada de maneira randomica (utilize uuid4 ou outro método de geração de chaves). + +Para testes e desenvolvimento você pode utilizar texto puro. **mas em produção escolha uma chave segura!** + +Além disso o Flask-Security precisa que seja especificado qual tipo de hash usar nos passwords. + +Adicione ao ``development_instance/config.cfg`` + +```python +SECRET_KEY = 'super-secret' +SECURITY_PASSWORD_HASH = 'pbkdf2_sha512' +SECURITY_PASSWORD_SALT = SECRET_KEY +``` + +> Importante se esta chave for perdida todas as senhas armazenadas serão invalidadas. + +### Definindo o schema dos usuários e grupos + +O Flask-Security permite o controle de acesso utilizando RBAC (Role Based Access Control), ou seja, usuários pertencem a grupos e os acessos são concedidos aos grupos. + +Para isso precisamos armazenar (no nosso caso no MongoDB) os usuários e seus grupos. + +Crie um novo arquivo **security_models.py** e criaremos duas classes **User** e **Role** + +```python +# coding: utf-8 +from .db import db +from flask_security import UserMixin, RoleMixin +from flask_security.utils import encrypt_password + + +class Role(db.Document, RoleMixin): + + name = db.StringField(max_length=80, unique=True) + description = db.StringField(max_length=255) + + @classmethod + def createrole(cls, name, description=None): + return cls.objects.create( + name=name, + description=description + ) + + +class User(db.Document, UserMixin): + name = db.StringField(max_length=255) + email = db.EmailField(max_length=255, unique=True) + password = db.StringField(max_length=255) + active = db.BooleanField(default=True) + confirmed_at = db.DateTimeField() + roles = db.ListField( + db.ReferenceField(Role, reverse_delete_rule=db.DENY), default=[] + ) + last_login_at = db.DateTimeField() + current_login_at = db.DateTimeField() + last_login_ip = db.StringField(max_length=255) + current_login_ip = db.StringField(max_length=255) + login_count = db.IntField() + + @classmethod + def createuser(cls, name, email, password, + active=True, roles=None, username=None, + *args, **kwargs): + return cls.objects.create( + name=name, + email=email, + password=encrypt_password(password), + active=active, + roles=roles, + username=username, + *args, + **kwargs + ) +``` + +O arquivo acima define os models com todas as propriedades necessárias para que o Flask-Security funcione com o MongoEngine, não entrerei em detalhes de cada campo pois usaremos somente o básico neste tutorial, acesse a documentação do Flask-Security se desejar saber mais a respeitod e cada atributo. + +### Inicializando o Flask Security em seu projeto + +Da mesma forma que fizemos com as outras extensões iremos fazer como security, alterando o arquivo **news_app.py** e inicializando a extensão utilizando o método default. + +Importaremos o **Security** e o **MongoEngineUserDatastore** e inicializaremos a extensão passando nossos models de User e Role. + +```python +... +from flask_security import Security, MongoEngineUserDatastore +from .db import db +from .security_models import User, Role + +def create_app(mode): + ... + Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role)) + return app +``` + +**news_app.py** + +```python +# coding: utf-8 +from os import path +from flask import Flask +from flask_bootstrap import Bootstrap +from flask_security import Security, MongoEngineUserDatastore + +from .blueprints.noticias import noticias_blueprint +from .db import db +from .security_models import User, Role + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + db.init_app(app) + Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role)) + return app +``` + +Pronto, agora temos nossa base de usuários e grupos definida e o Security irá iniciar em nosso app todo o restante necessário para o controle de login (session, cookies, formulários etc..) + +#### Exigindo login para cadastro de notícia + +Altere a view de cadastro em ``blueprints/noticias.py`` e utilize o decorator ``login_required`` que é disponibilizado pelo Flask-Security, sendo que o inicio do arquivo ficará assim: + +```python +# coding: utf-8 +import os +from werkzeug import secure_filename +from flask import ( + Blueprint, request, current_app, send_from_directory, render_template +) +from ..models import Noticia +from flask_security import login_required # decorator + +noticias_blueprint = Blueprint('noticias', __name__) + + +@noticias_blueprint.route("/noticias/cadastro", methods=["GET", "POST"]) +@login_required # aqui o login será verificado +def cadastro(): + ... +``` + +Execute ``python run.py`` acesse [http://localhost:5000/noticias/cadastro](http://localhost:5000/noticias/cadastro) e verifique que o login será exigido para continuar. + +> NOTE: Se por acaso ocorrer um erro **TypeError: 'bool' object is not callable** execute o seguinte comando ``pip install Flask-Login==0.2.11`` e adicione ``Flask-Login==0.2.11`` no arquivo requirements.txt. Este erro ocorre por causa de um recente bug na nova versão do Flask-Login. + +Se tudo ocorrer como esperado agora você será encaminhado para a página de login. + +
    +login +
    + +O único problema é que você ainda não possui um usuário para efetuar o login. Em nosso model de User definimos um método **create_user** que pode ser utilizado diretamente em um terminal iPython. Porém o Flask-Security facilita bastante fornecendo também um formulário de registro de usuários. + +Adicione as seguintes configurações no arquivo ``development_instance/config.cfg`` para habilitar o formulário de registro de usuários. + +```python +SECURITY_REGISTERABLE = True +SECURITY_TRACKABLE = True # para armazenar data, IP, ultimo login dos users. + +# as opções abaixo devem ser removidas em ambiente de produção +SECURITY_SEND_REGISTER_EMAIL = False +SECURITY_LOGIN_WITHOUT_CONFIRMATION = True +SECURITY_CHANGEABLE = True +``` + +Agora acesse [http://localhost:5000/register](http://localhost:5000/register) e você poderá registar um novo usuário e depois efetuar login. + +
    +register +
    + +> NOTE: É recomendado que a opção de registro de usuário seja desabilidata em ambiente de produção, que seja utilizado outros meios como o Flask-Admin que veremos adiante para registrar novos usuários ou que seja habilitado o Captcha para os formulários de registro e login e também o envio de email de confirmação de cadastro. + +Todas as opções de configuração do Flsk-Security estão disponíveis em [https://pythonhosted.org/Flask-Security/configuration.html](https://pythonhosted.org/Flask-Security/configuration.html) + +Agora será interessante mostrar opções de Login, Logout, Alterar senha na barra de navegação. Para isso altere o template ``base.html`` adicionando o bloco de access control. + +```html +{%- extends "bootstrap/base.html" %} +{% import "bootstrap/utils.html" as utils %} +{% block title %} {{title or "Notícias"}} {% endblock %} +{% block navbar -%} + +{%- endblock navbar %} +{% block content %} +
    + {%- with messages = get_flashed_messages(with_categories=True) %} + {%- if messages %} +
    +
    + {{utils.flashed_messages(messages)}} +
    +
    + {%- endif %} + {%- endwith %} +
    + {% block news %} + {% endblock %} +
    +{%- endblock content%} +``` + +O resultado final será: + +
    +access +
    + +As opções de customização e instruções de como alterar os formulários e templates do Flask-Security encontram-se na [documentação oficial](https://pythonhosted.org/Flask-Security/). + +> O diff com todas as alterações feitas com o Flask-Security pode ser consultado neste [link](https://github.com/rochacbruno/wtf/commit/3766bfabb6d9c359731ff3a143101209af0d207f) + +## Flask Admin - Um admin tão poderoso quanto o Django Admin! + +
    +admin +
    + +Todos sabemos que uma das grandes vantagens de um framework full-stack como Django ou Web2py é a presença de um Admin para o banco de dados. Mesmo sendo Micro-Framework o Flask conta com a extensão Flask-Admin que o transforma em uma solução tão completa quanto o Django-Admin. + +O Flask-Admin é uma das mais importantes extensões para o Flask e é frequentemente atualizada e tem uma comunidade muito ativa! O AirBnb recentemente lançou o [AirFlow que utiliza o Flask-Admin](http://mrjoes.github.io/2015/06/17/flask-admin-120.html) + +E o [QuokkaCMS](http://www.quokkaproject.org), principal CMS desenvolvido com Flask e MongoDB é baseado também no Flask-Admin. + +Para começar vamos colocar os requisitos no arquivo de requirements!! + +**requirements.txt** + +``` +https://github.com/mitsuhiko/flask/tarball/master +flask-mongoengine +nose +Flask-Bootstrap +Flask-Security +Flask-Login==0.2.11 +Flask-Admin +``` + +Instalar com ``pip install -r requirements.txt --upgrade`` + +### Admin para o seu banco de dados MongoDB!!! + +O Flask-Admin é um painel administrativo para bancos de dados de seus projetos Flask e ele tem suporte a diversos ORMs e Tecnologias como MySQL, PostGres, SQLServer e ORMs SQLAlchemy, Peewee, PyMongo e MongoEngine. + +O Flask Admin utiliza o Bootstrap por padrão para a camada visual do admin, mas é possivel customizar com o uso de temas. + +A primeira coisa a ser feita depois de ter o Flask-Admin instalado é inicializar o admin da mesma maneira que fizemos com as outras extensões. + +Vamos adicionar as seguintes linhas ao arquivo **news_app.py** + +```python +from flask_admin import Admin +... + +def create_app(mode): + ... + admin = Admin(app, name='Noticias', template_mode='bootstrap3') + return app +``` + +Ficando o arquivo completo. + +```python +# coding: utf-8 +from os import path +from flask import Flask +from flask_bootstrap import Bootstrap +from flask_security import Security, MongoEngineUserDatastore +from flask_admin import Admin + +from .blueprints.noticias import noticias_blueprint +from .db import db +from .security_models import User, Role + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + db.init_app(app) + Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role)) + admin = Admin(app, name='Noticias', template_mode='bootstrap3') + return app +``` + +Agora basta executar ``python run.py`` e acessar [http://localhost:5000/admin/](http://localhost:5000/admin/) e você verá a tela **index** do Flask-Admin. + +
    +admin_index +
    + + +Se você conseguir acessar a tela acima então o Flask-Admin está inicializado corretamente, perceba que não tem nada além de uma tela em branco e um botão "home". + +Precisamos agora registrar nossas ModelViews que são as telas de administração para cada coleção ou tabela do banco de dados e também implementar a integração com o Flask-Security para garantir que somente pessoas autorizadas acessem o admin. + +#### Menu de controle de acesso + +Em nosso front-end incluimos na barra de menus os links de controle de acesso **login**, **alterar senha** e **logout**, precisamos agora incluir os mesmos itens na barra de menus do Flask-Admin. + +Para começar crie um template novo em **templates/admin_base.html** com o seguinte conteúdo. + +```html +{% extends 'admin/base.html' %} + +{% block access_control %} + +{% endblock %} +``` + +Agora altere o template base do Flask-Admin incluindo o parametro ``base_template='admin_base.html'`` no **news_app.py** + +``` +def create_app(mode): + ... + admin = Admin(app, name='Noticias', template_mode='bootstrap3', + base_template='admin_base.html') + return app +``` + +
    +admin_index_login +
    + +O Flask-Admin não possui uma forma automática de integração com o Flask-Security, porém podemos facilmente sobrescrever a classe de ModelView incluindo o controle de acesso necessário. + +Para que isso fique mais fácil vamos centralizar a configuração do Flask-Admin em um único arquivo. + +Crie um arquivo chamado **wtf/admin.py** na raiz do projeto, iremos extender a ModelView do Flask-Admin tornando-a segura e exigindo login e também iremos registrar os models **Noticia**, **User** e **Role** em nosso painel de adminsitração. + +> NOTE: Se desejar que apenas usuários que pertençam ao grupo **admin** tenham acesso descomente as linhas do método **is_acessible** + +**wtf/admin.py** +```python +# coding: utf-8 +from flask import abort, redirect, request, url_for +from flask_security import current_user +from flask_admin.contrib.mongoengine import ModelView +from flask_admin import Admin + +from .models import Noticia +from .security_models import User, Role + + +admin = Admin(name='Noticias', template_mode='bootstrap3', + base_template='admin_base.html') + + +# Create customized model view class +class SafeModelView(ModelView): + + def is_accessible(self): + if not current_user.is_authenticated(): + return False + # if not current_user.has_role('admin'): + # return False + return True + + def _handle_view(self, name, **kwargs): + """ + Redireciona o usuário para página de login ou de acesso negado + """ + if not self.is_accessible(): + if current_user.is_authenticated(): + abort(403) # negado, caso não pertença ao grupo admin. + else: + return redirect(url_for('security.login', next=request.url)) + + +def configure_admin(app): + admin.init_app(app) + admin.add_view(SafeModelView(Noticia)) + admin.add_view(SafeModelView(User, category='accounts')) + admin.add_view(SafeModelView(Role, category='accounts')) +``` + +Agora só precisamos substituir a maneira como inicializamos o admin pela chamada a função **configure_admin**. + +No arquivo **news_app.py** troque a parte + +```python +from flask_admin import Admin +... + +def create_app(mode): + ... + admin = Admin(name='Noticias', template_mode='bootstrap3', + base_template='admin_base.html') + return app +``` + +Pelo uso da função **configure_admin** + +```python +from .admin import configure_admin +... + +def create_app(mode): + ... + configure_admin(app) + return app +``` + +Desta forma ao executar ``python run.py`` e acessar [http://localhost:5000/admin/](http://localhost:5000/admin/) estando logado você irá os menus referentes aos nossos models e poderá editar/apagar/adicionar novos usuários e notícias. + +
    +admin_noticia +
    + +O Flask-admin possui diversas opções de customização de template, formulários, permissões. Você pode limitar quais campos exibir, alterar o comportamento dos formulários e até mesmo incluir views e formulários que não estejam no banco de dados. + +### Limitando os campos a serem exibidos. + +Atualmente clicando no menu **accounts/user** a lista exibe vários campos referentes ao cadastro de usuários (incluindo a senha encriptada). + +
    +admin_user_full +
    + +Altere nosso arquivo **admin.py** e crie uma nova classe **UserModelView** onde limitaremos os campos que serão listados no admin. + +**wtf/admin.py** + +```python +... + +class UserModelView(SafeModelView): + column_list = ("name", "email", "active", "last_login_at", "login_count") + +... +``` + +E agora no final do arquivo utilize esta classe ao adicionar a view. + +```python +... +admin.add_view(UserModelView(User, category='accounts')) +... +``` + +> NOTE: Não iremos nos aprofundar em todas as opções de customização do Flask-admin, portanto consulte a [documentação](https://flask-admin.readthedocs.org/) para saber mais. + +
    +admin_user_columns +
    + +> O diff com todas as alterações referentes ao Flask-Admin estão no commit [1359c4cf9a31a0d471a3226a1bec2a672f9ffbbb](https://github.com/rochacbruno/wtf/commit/1359c4cf9a31a0d471a3226a1bec2a672f9ffbbb) + +## Flask Cache - Deixando o MongoDB "de boas" + + +A cada vez que acessamos a home do nosso site uma consulta é feita ao MongoDB, e isso está definido em **blueprints/noticias.py** + +```python +@noticias_blueprint.route("/") +def index(): + todas_as_noticias = Noticia.objects.all() + return render_template('index.html', + noticias=todas_as_noticias, + title=u"Todas as notícias") +``` + +Na linha ``todas_as_noticias = Noticia.objects.all()`` fazemos uma query ao MongoDB, se 10.000 usuários acessarem a nossa página 10.000 acessos serão feitos ao MongoDB. + +O mesmo acontece na view de acesso a uma notícia ``noticia = Noticia.objects.get(id=noticia_id)`` vai até o MongoDB e faz a query procurando a notícia pelo **id** e teremos novamente problemas se por exemplo muitos usuários acessarem a mesma notícia ao mesmo tempo. + +Podemos otimizar as queries no Mongo utilizando **indices** porém o mais indicado é o uso de cache. + +
    +deboas +
    + +Em ambiente de alta disponibilidade é altamente recomendado usar um servidor de cache como o **Varnish** para servir de camada intermediária ou até mesmo gerar páginas HTML estáticas de cada uma das notícias. + +Mas em caso de sites menores podermos contar com um sistema de cache mais simples utilizando MemCached, Redis ou até mesmo sistema de arquivos para armazenar o cache. + +Em nosso projeto usaremos o Flask-Cache que poderá ser usado com Redis ou da maneira simples utilizando o sistema de arquivos. + +### Debug Toolbar + +Antes de mais nada precisamos de uma ajuda para enxergarmos o problema, vamos utilizar a Flask-DebugToolbar para nos mostrar o custo de nossas idas ao banco de dados e outras coisas uteis para o desenvolvimento em Flask. + +Adicione ao arquivo **requirements.txt** a **flask-debugtoolbar** e utilize o flask-mongoengine diretamente do github. + +``` +https://github.com/mitsuhiko/flask/tarball/master +https://github.com/MongoEngine/flask-mongoengine/tarball/master +nose +Flask-Bootstrap +Flask-Security +Flask-Login==0.2.11 +Flask-Admin +flask-debugtoolbar +flask-cache +``` + +> NOTE: usaremos o flask-mongoengine diretamente do github **https://github.com/MongoEngine/flask-mongoengine/tarball/master** pois a versão do PyPI ainda não está compatível com a debug-toolbar + +E atualize sua **env** ``pip install -r requirements.txt --upgrade`` + +Agora edite o arquivo **development_instance/config.cfg** adicionando as seguintes entradas. + +**development_instance/config.cfg** +```python +DEBUG = True +DEBUG_TOOLBAR_ENABLED = True +DEBUG_TB_INTERCEPT_REDIRECTS = False +DEBUG_TB_PROFILER_ENABLED = True +DEBUG_TB_TEMPLATE_EDITOR_ENABLED = True +DEBUG_TB_PANELS = ( + 'flask_debugtoolbar.panels.versions.VersionDebugPanel', + 'flask_debugtoolbar.panels.timer.TimerDebugPanel', + 'flask_debugtoolbar.panels.headers.HeaderDebugPanel', + 'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel', + 'flask_debugtoolbar.panels.template.TemplateDebugPanel', + 'flask_mongoengine.panels.MongoDebugPanel', + 'flask_debugtoolbar.panels.logger.LoggingPanel', + 'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel', + 'flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel', +) +``` + +Inicialize a extensão no arquivo **news_app.py** + + +```python +... +from flask_debugtoolbar import DebugToolbarExtension +... + +def create_app(mode): + ... + DebugToolbarExtension(app) + return app +``` + +Agora execute o projeto ``python run.py`` e navegue pelo site e pelo admin e analise os painéis do Flask-DebugToolbar que aparecerão na lateral direita. + +
    +debug_toolbar +
    + +Note que temos vários painéis de DEBUG incluindo o painel MongoDB exibindo o tempo consumido para o acesso ao banco de dados, clicando neste botão da toolbar você poderá visualizar as queries que foram feitas ao MongoDB. + +E também é interessante analisar o botão **Profiler** que exibe e o consumo de memória e CPU em cada parte de nosso app. + +
    +profiler +
    + +No nosso caso como estamos em ambiente de desenvolvimento e com apenas um usuário fazendo requests os números serão insignificantes, porém basta colocar em produção e ter um **slashdot effect** que as coisas começarão a complicar. + +Portanto vamos utilizar o **Flask-Cache** para minimizar o acesso ao MongoDB. + +O Flask-Cache possui integração com alguns sistemas de cache e são eles: + +Built-in cache types: + +- null: NullCache (default) +- simple: SimpleCache +- memcached: MemcachedCache (pylibmc or memcache required) +- gaememcached: GAEMemcachedCache +- redis: RedisCache (Werkzeug 0.7 required) +- filesystem: FileSystemCache +- saslmemcached: SASLMemcachedCache (pylibmc required) + +> NOTE: O mais recomendado é o uso de **Redis** ou **memcached** mas como isso exige a instalação de libs adicionais utilizaremos o **FileSystemCache** em nosso exemplo, porém o uso de cache em file system pode ser até mais lento que o acesso direto ao banco, portanto vamos utiliza-lo somente como exemplo. + +Crie um arquivo **cache.py** na raiz do projeto: + +```python +from flask_cache import Cache +cache = Cache(config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': '/tmp'}) +``` + +Vamos inicializar a extensão da mesma forma que fizemos com as outras e neste ponto nosso arquivo **news_app.py** deverá estar assim: + +```python +# coding: utf-8 +from os import path +from flask import Flask +from flask_bootstrap import Bootstrap +from flask_security import Security, MongoEngineUserDatastore +from flask_debugtoolbar import DebugToolbarExtension + +from .admin import configure_admin +from .blueprints.noticias import noticias_blueprint +from .db import db +from .security_models import User, Role +from .cache import cache + + +def create_app(mode): + instance_path = path.join( + path.abspath(path.dirname(__file__)), "%s_instance" % mode + ) + + app = Flask("wtf", + instance_path=instance_path, + instance_relative_config=True) + + app.config.from_object('wtf.default_settings') + app.config.from_pyfile('config.cfg') + + app.config['MEDIA_ROOT'] = path.join( + app.config.get('PROJECT_ROOT'), + app.instance_path, + app.config.get('MEDIA_FOLDER') + ) + + app.register_blueprint(noticias_blueprint) + + Bootstrap(app) + db.init_app(app) + Security(app=app, datastore=MongoEngineUserDatastore(db, User, Role)) + configure_admin(app) + DebugToolbarExtension(app) + cache.init_app(app) + return app +``` + +Pronto, agora podemos começar a utilizar o cache nas views e templates. + +> NOTE: Escrevi um artigo explicando o Flask-Cache com mais detalhes que está disponível [em meu blog](http://brunorocha.org/python/flask/usando-o-flask-cache.html) + +Vamos colocar cache nas views de acesso ao MongoDB importaremos o cache que criamos no arquivo **cache.py** e utilizaremos diretamente ou como forma de decorator. + + +**blueprints/noticias.py** + +```python +... +from ..cache import cache +... +``` + +Agora vamos utilizar o cache como decorator para cachear a view **index** durante 5 minutos utilizando ``@cache.cached(timeout=300)`` para decorar a view. + +```python +@noticias_blueprint.route("/") +@cache.cached(timeout=300) +def index(): + todas_as_noticias = Noticia.objects.all() + return render_template('index.html', + noticias=todas_as_noticias, + title=u"Todas as notícias") + +``` + +Agora acesse [localhost:5000](http://localhost:5000) e repare que no primeiro acesso o MongoDB será acessado mas quando der refresh (f5) agora o acesso não será mais feito durante 5 minutos. + +
    +cached +
    + +Uma outra maneira de cachear é chamando o cache diretamente, vamos fazer isso na view **noticia** usando **cache.get** e **cache.set** + +```python +@noticias_blueprint.route("/noticia/") +def noticia(noticia_id): + noticia_cacheada = cache.get(noticia_id) + if noticia_cacheada: + noticia = noticia_cacheada + else: + noticia = Noticia.objects.get(id=noticia_id) + cache.set(noticia_id, noticia, timeout=300) + return render_template('noticia.html', noticia=noticia) +``` + +Além das dessas duas maneiras também é possível cachear blocos de template e memoizar funções que recebem argumentos. + +> BEWARE: Utilizar cache e controle de acesso é algo que deve ser feito com cuidado em nosso exemplo se um usuário autenticado acessar uma notícia com acesso controlado provavelmente o cache irá armazenar esta versão e todos os outros usuários terão acesso. Portanto se este for o seu caso, utilize o nome do usuário ou grupo como chave do cache. + +Se quiser saber mais detalhes sobre o Flask-Cache consulte a postagem que fiz em meu [blog](http://brunorocha.org/python/flask/usando-o-flask-cache.html) e a [documentação oficial](https://pythonhosted.org/Flask-Cache/). + +> O diff com as alterações realizadas com o Flask-Cache encontra-se em [27bacd25a788ffc041de332403a2426cd199b828](https://github.com/rochacbruno/wtf/commit/27bacd25a788ffc041de332403a2426cd199b828) + +Algumas outras extensões recomendadas que não foram abordadas neste artigo + + +- [Flasgger](https://github.com/rochacbruno/flasgger) Para criar APIs com documentaçãi via Swagger UI +- [Flask Google Maps](http://github.com/rochacbruno/Flask-GoogleMaps) Para inserir mapas facilmente em apps Flask +- [Flask Dynaconf](https://github.com/rochacbruno/dynaconf) Para configurações dinâmicas +- [Flask Email] Para avisar os autores que tem novo comentário +- [Flask Queue/Celery] Pare enviar o email assincronamente e não bloquear o request +- [Flask Classy] Um jeito fácil de criar API REST e Views +- [Flask Oauth e OauthLib] Login com o Feicibuque e tuinter + +> A versão final do app está no [github](https://github.com/rochacbruno/wtf/tree/extended) + +
    + +> **END:** Sim chegamos ao fim desta terceira parte da série **W**hat **T**he **F**lask. Eu espero que você tenha aproveitado as dicas aqui mencionadas. Nas próximas 2 partes iremos desenvolver nossas próprias extensões e blueprints e também questṍes relacionados a deploy de aplicativos Flask. Acompanhe o PythonClub, o meu [site](http://brunorocha.org) e meu [twitter](http://twitter.com/rochacbruno) para ficar sabendo quando a próxima parte for publicada. + +
    + +> **PUBLICIDADE:** Iniciarei um curso online de Python e Flask, para iniciantes abordando com muito mais detalhes e exemplos práticos os temas desta série de artigos e muitas outras coisas envolvendo Python e Flask, o curso será oferecido no CursoDePython.com.br, ainda não tenho detalhes especificos sobre o valor do curso, mas garanto que será um preço justo e acessível. Caso você tenha interesse por favor preencha este [formulário](https://docs.google.com/forms/d/1qWx4pzNVSPQmxsLgYBjTve6b_gGKfKLMSkPebvpMJwg/viewform?usp=send_form) pois dependendo da quantidade de pessoas interessadas o curso sairá mais rapidamente. + +
    + +> **PUBLICIDADE 2:** Também estou escrevendo um livro de receitas **Flask CookBook** através da plataforma LeanPub, caso tenha interesse por favor preenche o formulário na [página do livro](https://leanpub.com/pythoneflask) + +
    + +> **PUBLICIDADE 3:** Inscreva-se no meu novo [canal de tutoriais](http://www.youtube.com/channel/UCKkjiNMtdyCOFE3-w7TB8xw?sub_confirmation=1) + + +Muito obrigado e aguardo seu feedback com dúvidas, sugestões, correções etc na caixa de comentários abaixo. + +Abraço! "Python é vida!" + diff --git a/contributing.md b/contributing.md index 2c0576f0a..469ccc3a9 100644 --- a/contributing.md +++ b/contributing.md @@ -7,5 +7,12 @@ Ao contribuir com artigos neste repositorio, você concorda em licenciar seu art ## Regras de contribuição - . A data/hora contida na tag Markdown ```Date:``` ou na tag reStructuredText ```:date:``` deve ser posterior a do ultimo artigo publicado, - de modo que não se perca a ordem cronologica das postagems. + . A data/hora contida na tag Markdown ```Date:``` ou na tag reStructuredText ```:date:``` deve ser posterior a do ultimo artigo publicado, de modo que não se perca a ordem cronologica das postagems. + +## Orientações de como deve ser feita traduções + +Para seguir um padrão conciso de tradução, nós adotamos esse guia: + +http://python.pro.br/pydoc/2.7/tutorial/TERMINOLOGIA.html + +http://python.pro.br/pydoc/2.7/tutorial/NOTAS.html diff --git a/custom-plugins/json_articles/Readme.md b/custom-plugins/json_articles/Readme.md new file mode 100644 index 000000000..fe390d036 --- /dev/null +++ b/custom-plugins/json_articles/Readme.md @@ -0,0 +1,35 @@ +Json Articles Plugin For Pelican +======================== + +This plugin insert a variable called `json_articles` in the page context. + +This variable contains all articles in a json format. + + +In settings we can define the number of random_articles: + + RANDOM_ARTICLES = 3 + + +Usage +----- + +Add `dynamic_random_articles.js` in your template, in our case, just do that: + + +In our sidebar.html was inserted: + + div class="section articles-random"> +

    Não deixe de ver!

    +
    +
    + +We use `articles-random` class to hide it in mobile. + +And then, we call the javascript function that render articles: + + $(function(){ + show_random_articles($('#random-articles'), {{ json_articles }}, {{ RANDOM_ARTICLES }}); + }); + +Note that we pass a element to function, this element can be changed any time. diff --git a/custom-plugins/json_articles/__init__.py b/custom-plugins/json_articles/__init__.py new file mode 100644 index 000000000..ffe503b85 --- /dev/null +++ b/custom-plugins/json_articles/__init__.py @@ -0,0 +1 @@ +from .json_articles import * diff --git a/custom-plugins/json_articles/json_articles.py b/custom-plugins/json_articles/json_articles.py new file mode 100644 index 000000000..c5704d4f8 --- /dev/null +++ b/custom-plugins/json_articles/json_articles.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import json + +from pelican import signals + + +def inject_articles(generator, metadata=None): + articles = generator.context['articles'] + site_url = generator.settings['SITEURL'] + + json_articles = [] + + for article in articles: + json_articles.append({ + 'title': article.title, + 'url': '{}/{}'.format(site_url, article.url) + }) + + generator.context['json_articles'] = json.dumps(json_articles) + + +def register(): + signals.page_generator_context.connect(inject_articles) diff --git a/deploy.sh b/deploy.sh index c00fad303..62566c6d9 100644 --- a/deploy.sh +++ b/deploy.sh @@ -23,5 +23,7 @@ if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then git add --all -f . git commit -m "Travis build $TRAVIS_BUILD_NUMBER pushed to Github Pages" git push -fq origin $BRANCH > /dev/null + curl -Is http://www.google.com/webmasters/tools/ping?sitemap=http://pythonclub.com.br/sitemap.xml | grep "200 OK" || echo "Erro pinging Google" + curl -Is http://www.bing.com/webmaster/ping.aspx?siteMap=http://pythonclub.com.br/sitemap.xml | grep "200 OK" || echo "Erro pinging Bing" echo -e "Deploy completed\n" fi diff --git a/pelican-plugins b/pelican-plugins index c826c8d75..9b4536ca3 160000 --- a/pelican-plugins +++ b/pelican-plugins @@ -1 +1 @@ -Subproject commit c826c8d75089d74a341746e85c89166c99d5bc4d +Subproject commit 9b4536ca32977d76bdc05412bbc4566d2eabca21 diff --git a/pelicanconf.py b/pelicanconf.py index 7e0a2221c..9fc878781 100644 --- a/pelicanconf.py +++ b/pelicanconf.py @@ -2,13 +2,15 @@ # -*- coding: utf-8 -*- # from __future__ import unicode_literals import os + + BASE = os.path.dirname(__file__) AUTHOR = u'PythonClub' AUTHOR_EMAIL = u'gravatar@pythonclub.com.br' SITENAME = u'PythonClub' -SITEURL = 'http://pythonclub.com.br/' -SITELOGO = 'http://res.cloudinary.com/diu8g9l0s/image/upload/v1400201393/pythonclub/logo_275x130.png' +SITEURL = 'https://pythonclub.com.br/' +SITELOGO = 'https://res.cloudinary.com/diu8g9l0s/image/upload/v1400201393/pythonclub/logo_275x130.png' GITHUB_URL = 'https://github.com/pythonclub/pythonclub.github.io' DISQUS_SITENAME = 'pythonclub' @@ -54,19 +56,24 @@ # Plugins PLUGIN_PATHS = [ - 'pelican-plugins' + 'pelican-plugins', + 'custom-plugins' ] PLUGINS = [ 'gravatar', + 'pelican_alias', # para criar alias para artigos 'sitemap', - 'pelican_youtube', # funciona somente com arquivos rst - 'pelican_vimeo', # funciona somente com arquivos rst - 'gzip_cache', # deve ser o ultimo plugin + 'pelican_youtube', # funciona somente com arquivos rst + 'pelican_vimeo', # funciona somente com arquivos rst + 'json_articles', + 'gzip_cache' # deve ser o ultimo plugin # 'pdf', # funciona somente com arquivos rst ] +RANDOM_ARTICLES = 10 + SITEMAP = { 'format': 'xml', @@ -86,7 +93,7 @@ THEME = 'theme' # Theme Pure config -PROFILE_IMAGE_URL = "http://res.cloudinary.com/diu8g9l0s/image/upload/v1399566411/fundo_python_a6iqip.png" +PROFILE_IMAGE_URL = "https://res.cloudinary.com/diu8g9l0s/image/upload/v1399566411/fundo_python_a6iqip.png" # Uncomment following line if you want document-relative URLs when developing RELATIVE_URLS = True diff --git a/requirements.txt b/requirements.txt index 75f208c31..ec17ce608 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ --e git+https://github.com/getpelican/pelican.git#egg=pelican -#pelican==3.3.0 +#-e git+https://github.com/getpelican/pelican.git#egg=pelican +pelican==3.7.1 Markdown pelican-youtube pelican-vimeo +#pelican-alias +-e git+https://github.com/luzfcb/pelican-alias.git@fix-wrong-alias-filename-output#egg=pelican-alias # rst2pdf diff --git a/theme/static/css/custom.css b/theme/static/css/custom.css index ee343a07c..76485dd1b 100644 --- a/theme/static/css/custom.css +++ b/theme/static/css/custom.css @@ -73,8 +73,6 @@ code { padding-bottom: 1px; } .section img { - width: 50%; - max-width: 250px; display: block; margin-left: auto; margin-right: auto; @@ -85,6 +83,16 @@ code { width: 80%; } +.articles-random { + font-size: 0.8em; +} + +.article-link { + margin-top: 0px; + line-height: 1em; +} + + @media (max-width: 767px) { .brand-main a img { display: inline; @@ -96,12 +104,15 @@ code { .tagline { margin-bottom: -20px; } - .section img { - display: none; - } .google-search { width: 100%; } + + .articles-random, .articles-link { + display: none; + } + + } @media (max-width: 480px) { @@ -115,10 +126,6 @@ code { .tagline { margin-bottom: -10px; } - .section img { - display: none; - width: 30px; - } .publish-github { display: none; } diff --git a/theme/static/css/pure.css b/theme/static/css/pure.css index ce58eb811..d42962afb 100644 --- a/theme/static/css/pure.css +++ b/theme/static/css/pure.css @@ -308,6 +308,7 @@ pre { .content { padding: 1em 1.5em 0; font-size: 85%; + word-wrap: break-word; } .cover-img { diff --git a/theme/static/js/dynamic_random_articles.js b/theme/static/js/dynamic_random_articles.js new file mode 100644 index 000000000..5be7a564d --- /dev/null +++ b/theme/static/js/dynamic_random_articles.js @@ -0,0 +1,26 @@ +function shuffleArray(array) { + for (var i = array.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + return array; +} + +function show_random_articles(element, articles, articles_number){ + articles = shuffleArray(articles); + + var paragraph = $('

    '); + + for (index in articles){ + var article = articles[index]; + var article_html = '' + article.title + ''; + var article_paragraph = paragraph.clone().append(article_html) + element.append(article_paragraph); + + if (index >= articles_number -1) + break; + } + +} diff --git a/theme/templates/base.html b/theme/templates/base.html index e09d21729..7857d7933 100644 --- a/theme/templates/base.html +++ b/theme/templates/base.html @@ -4,6 +4,7 @@ + {% if FEED_ALL_ATOM %} @@ -28,22 +29,25 @@ + {% block head_css %}{% endblock %} {% block head_js %}{% endblock %} {% include 'analytics.html' %} - - - + + + + + + + + + + + + @@ -52,6 +56,8 @@ + + {% block bottom_js %}{% endblock %} + {% block spot_im %} + + + + + {% endblock %} + {% block gitter %} + + + {% endblock %} diff --git a/theme/templates/sidebar.html b/theme/templates/sidebar.html index 6f4c33e2f..797cb731a 100644 --- a/theme/templates/sidebar.html +++ b/theme/templates/sidebar.html @@ -8,6 +8,12 @@

    {{ SITENAME }}
           {% for title, link in MENUITEMS %}
           <p class={{ title }}

    {% endfor %} + +
    +

    Não deixe de ver!

    +
    +
    +

    {{ SITENAME }}
             </a>
             {% endfor %}
           </p>
-          <div class= - #Python2014, vamos?! -
    - - Eu vou na PythonBrasil[10] - -
    -

    Esta comunidade é signatária do