├── .gitignore ├── README.md ├── anagrams ├── Anagramas-Slides_EN.ipynb ├── Anagrams-with-pytest.ipynb ├── README.md ├── ipython_pytest.py ├── kata.py ├── requirements.txt └── tests.py ├── base ├── README.md ├── kata.py └── tests.py ├── chalice ├── README.md ├── requirements.txt └── tank │ ├── .gitignore │ ├── app.py │ └── requirements.txt ├── coins ├── Coins Kata Slides.ipynb ├── Coins Kata Solved.ipynb ├── Python and pytest intro with Coins Kata.ipynb ├── README.md ├── kata.py ├── tests.py └── tests_EP2015.py ├── concurrency ├── concurrency.org └── images │ ├── Thats_all_folks.svg │ ├── clock.jpg │ ├── cpu_i7.jpg │ ├── io.jpg │ └── man.jpg ├── cracking_classical_ciphers ├── caesar_cipher.ipynb ├── caesar_cipher_solution.ipynb ├── img │ ├── caesar_cipher.png │ ├── scytale.png │ ├── substitution_cipher.png │ └── vigenere.jpg ├── requirements.txt ├── scytale.ipynb ├── scytale_solution.ipynb ├── substitution_cipher.ipynb ├── substitution_cipher_solution.ipynb ├── substitution_decode_example.ipynb ├── vigenere_cipher.ipynb ├── vigenere_cipher_example.ipynb ├── vigenere_cipher_solution_1.ipynb └── vigenere_cipher_solution_2.ipynb ├── doc ├── Mocking.ipynb └── requirements.txt ├── dojos.css ├── factor-out ├── .gitignore ├── 1. Weather.ipynb ├── football.dat └── weather.dat ├── firebase-push-notifications ├── .gitignore ├── __init__.py ├── api.py ├── app.py ├── models.py ├── notifications.db ├── notifier.py ├── requirements.txt ├── slides.md ├── static │ ├── css │ │ └── styles.css │ ├── favicon.ico │ ├── js │ │ ├── firebase-messaging-sw.js │ │ └── index.js │ └── manifest.json ├── templates │ ├── index.html │ └── list.html └── views.py ├── flask-pythonanywhere ├── app_01_minimal.py ├── app_02_start.py ├── app_03_templates.py ├── app_04_routes.py ├── notes-export │ ├── css │ │ ├── print │ │ │ └── paper.css │ │ ├── reveal.css │ │ └── theme │ │ │ └── dark.css │ ├── favicon.ico │ ├── index.html │ ├── lib │ │ ├── css │ │ │ ├── darcula.css │ │ │ └── darkula.css │ │ └── js │ │ │ └── head.min.js │ └── plugin │ │ ├── highlight │ │ └── highlight.js │ │ ├── markdown │ │ ├── markdown.js │ │ └── marked.js │ │ ├── math │ │ └── math.js │ │ └── notes │ │ ├── notes.html │ │ └── notes.js ├── notes.md ├── templates │ └── home01.html └── translate │ ├── __init__.py │ ├── app.py │ ├── db.py │ └── translate.db ├── flaskgame ├── README.md ├── __init__.py ├── src │ ├── __init__.py │ ├── endpoint.py │ ├── fightvalue.py │ ├── punchservice.py │ ├── randompunchservice.py │ ├── static │ │ └── css │ │ │ └── style.css │ └── templates │ │ └── index.html └── tests │ ├── __init__.py │ ├── fighttest.py │ ├── hello.py │ └── mockpunchservice.py ├── functional_python ├── coordinates_kata.py ├── country-capitals.csv ├── jupyter_notebooks │ └── Functional vs OOP.ipynb ├── recursion_limit.py ├── robot_fun.py ├── robot_oop.py └── test_robot_oop.py ├── gilded-rose ├── Gilded Rose Refactoring Kata.ipynb ├── Gilded Rose Refactoring Kata.slides.html ├── dojo_cycle.png ├── dojos.css ├── generate_fixture.py ├── generate_fixtures ├── gilded-rose-reading.html ├── gilded-rose.html ├── gilded-rose.org ├── gilded_rose.py ├── item.py ├── run_tests └── test_gilded_rose.py ├── graphql ├── ariadne-api-example │ ├── api.py │ ├── requirements.txt │ └── schema.graphql └── ariadne-api │ ├── api.py │ ├── requirements.txt │ └── schema.graphql ├── letterments ├── Letterments Kata Solution Combi1.ipynb ├── Letterments Kata Solution.ipynb ├── Letterments Kata.ipynb └── Letterments Kata.slides.html ├── nltk ├── README.md ├── environment.yml ├── gen_devex_corpus.py ├── notebooks │ ├── 2. Basic classifier with Devex corpus.ipynb │ └── 3. More advanced classifier with Devex corpus-Copy1.ipynb ├── tareas.md └── tmp │ └── devex-corpus.json ├── optimization ├── join.py ├── measure_join.py └── pystone.py ├── pos ├── README.md ├── kata.py └── tests.py ├── pyzmq-workshop ├── Pipfile ├── Pipfile.lock ├── cities.txt ├── dojos.css ├── helloworld_client.py ├── helloworld_server.py ├── hider.py ├── pub.py ├── pyzmq-workshop.html ├── pyzmq-workshop.org ├── seeker.py ├── sub.py ├── weather_client.py └── weather_server.py ├── sqlalchemy ├── db │ ├── books.db │ └── books.sql ├── no_orm │ ├── __init__.py │ ├── add_author.py │ ├── add_book.py │ ├── author.py │ ├── authors.py │ ├── book.py │ ├── books.py │ ├── del_authors.py │ ├── del_books.py │ ├── list_authors.py │ ├── list_books.py │ ├── update_authors.py │ └── update_books.py ├── notes.html ├── notes.org ├── slides.html ├── slides.org └── sqlalchemy │ ├── add.py │ ├── base.py │ ├── base.pyc │ ├── books.db │ ├── delete.py │ ├── filter.py │ ├── insert_test.py │ ├── list.py │ ├── models.py │ └── update.py ├── string-calculator-mock ├── README.md ├── StringCalculator.py ├── StringCalculator_tests.py ├── kata.py ├── requirements.txt └── tests.py ├── string-calculator ├── README.md ├── kata.py └── tests.py ├── tamagotchi ├── BDD and Tamagotchi Kata.ipynb ├── README.md ├── __init__.py ├── environment.yml ├── object_oriented │ ├── __init__.py │ ├── features │ │ ├── bed.feature │ │ ├── environment.py │ │ ├── feed.feature │ │ ├── live.feature │ │ ├── play.feature │ │ ├── poop.feature │ │ └── steps │ │ │ └── basic.py │ └── src │ │ ├── __init__.py │ │ ├── interface │ │ ├── __init__.py │ │ ├── addiction.py │ │ ├── digestivesystem.py │ │ ├── energy.py │ │ ├── limitedparameter.py │ │ └── parameter.py │ │ ├── lib │ │ ├── __init__.py │ │ ├── bodyenergy.py │ │ ├── gameaddiction.py │ │ ├── unitlimitedparameter.py │ │ ├── unitparameter.py │ │ └── usualdigestivesystem.py │ │ └── tamagotchi.py └── requirements.txt ├── test ├── tower_of_hanoi └── Kata Tower of Hanoi.ipynb ├── understanding-sockets ├── README.md ├── hellosocket │ ├── __init__.py │ ├── berkeleysocketclient.py │ ├── berkeleysocketserver.py │ ├── contract │ │ ├── __init__.py │ │ ├── berkeleyside.py │ │ ├── client.py │ │ ├── pseudoside.py │ │ └── server.py │ ├── multiconnsyncberkeleysocketserver.py │ ├── pseudosocketclient.py │ └── pseudosocketserver.py ├── requirements.txt └── tests │ ├── __init__.py │ ├── hello.py │ ├── test_berkeley.py │ └── test_pseudo.py └── webpy ├── README.md └── src ├── __init__.py ├── endpoint.py └── templates ├── http401.html ├── index.html ├── login.html └── private.html /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/.idea/ 3 | **/.idea/* 4 | **/.vscode/* 5 | .ipynb_checkpoints 6 | reveal.js 7 | .cache 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ======= 2 | pyDojos 3 | ======= 4 | 5 | Éstos son los dojos realizados con la comunidad [pybcn] en Barcelona. Cada dojo realizado se almacena separadamente en su directorio. 6 | 7 | Participantes 8 | ============= 9 | 10 | Cada uno de los grupos participantes bifurcará éste repositorio, clonará su versión, y creará una rama para su código: 11 | 12 | git clone git@github.com:/pyDojos 13 | git checkout -b - 14 | 15 | Durante la sesión, irán cumplimentando los cambios, a medida que consigan los puntos marcados en la kata, si los hubiera, o cuando más les interese: 16 | 17 | git commit -am "" 18 | 19 | Antes de cada retrospectiva, el grupo subirá sus cambios a su repositorio y solicitará la fusión con su propia rama. 20 | 21 | git push - 22 | 23 | 24 | Facilitador 25 | =========== 26 | 27 | Para crear un nuevo dojo, sólo hay que copiar el directorio `base` con el nombre del dojo correspondiente, y actualizar el README.md correspondiente. Por ejemplo, para la sesión del 4 de octubre de 2014: 28 | 29 | cp -r base 20141004 30 | 31 | y luego editar el fichero `20141004/README.md` con el enunciado correspondiente a la cata elegida. 32 | Una vez formados los grupos y repasada la kata, poniendo en común los detalles, el facilitador creará una rama por cada grupo. 33 | 34 | [pybcn]: http://pybcn.org 35 | -------------------------------------------------------------------------------- /anagrams/README.md: -------------------------------------------------------------------------------- 1 | # Kata Anagramas 2 | 3 | Esta kata consiste en preparar una función que genere una lista de todos los anagramas posibles sobre una palabra que recibirá por parámetro: 4 | 5 | >>> from kata import anagramize 6 | >>> anagramize('biro') 7 | ['biro', 'bior', 'brio', 'broi', 'boir', 'bori', 'ibro', 'ibor', 'irbo', 'irob', 'iobr', 'iorb', 'ribo', 'riob', 'rbio', 'rboi', 'roib', 'robi', 'oirb', 'oibr', 'orib', 'orbi', 'obir', 'obri'] 8 | -------------------------------------------------------------------------------- /anagrams/ipython_pytest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import sys 4 | from pathlib import Path 5 | 6 | import tempfile 7 | from IPython.core import magic 8 | from pytest import main as pytest_main 9 | 10 | 11 | TEST_MODULE_NAME = '_ipytesttmp' 12 | 13 | def pytest(line, cell): 14 | with tempfile.TemporaryDirectory() as root: 15 | oldcwd = os.getcwd() 16 | os.chdir(root) 17 | tests_module_path = '{}.py'.format(TEST_MODULE_NAME) 18 | try: 19 | Path(tests_module_path).write_text(cell) 20 | args = shlex.split(line) 21 | os.environ['COLUMNS'] = '80' 22 | pytest_main(args + [tests_module_path]) 23 | if TEST_MODULE_NAME in sys.modules: 24 | del sys.modules[TEST_MODULE_NAME] 25 | finally: 26 | os.chdir(oldcwd) 27 | 28 | def load_ipython_extension(ipython): 29 | magic.register_cell_magic(pytest) 30 | -------------------------------------------------------------------------------- /anagrams/kata.py: -------------------------------------------------------------------------------- 1 | def anagramize(word): 2 | pass 3 | -------------------------------------------------------------------------------- /anagrams/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | bleach==1.5.0 3 | decorator==4.0.11 4 | entrypoints==0.2.2 5 | html5lib==0.9999999 6 | ipykernel==4.5.2 7 | ipython==5.1.0 8 | ipython-genutils==0.1.0 9 | ipywidgets==5.2.2 10 | Jinja2==2.9.4 11 | jsonschema==2.5.1 12 | jupyter==1.0.0 13 | jupyter-client==4.4.0 14 | jupyter-console==5.0.0 15 | jupyter-core==4.2.1 16 | MarkupSafe==0.23 17 | mistune==0.7.3 18 | nbconvert==5.0.0 19 | nbformat==4.2.0 20 | notebook>=5.7.2 21 | pandocfilters==1.4.1 22 | pexpect==4.2.1 23 | pickleshare==0.7.4 24 | prompt-toolkit==1.0.9 25 | ptyprocess==0.5.1 26 | Pygments==2.1.3 27 | pyzmq==16.0.2 28 | qtconsole==4.2.1 29 | simplegeneric==0.8.1 30 | six==1.10.0 31 | terminado==0.6 32 | testpath==0.3 33 | tornado==4.4.2 34 | traitlets==4.3.1 35 | wcwidth==0.1.7 36 | widgetsnbextension==1.2.6 37 | -------------------------------------------------------------------------------- /anagrams/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestAnagrams(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /base/README.md: -------------------------------------------------------------------------------- 1 | Kata base 2 | ========= 3 | 4 | Ésta es la kata base para los dojos. 5 | -------------------------------------------------------------------------------- /base/kata.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/base/kata.py -------------------------------------------------------------------------------- /base/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestKata(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /chalice/README.md: -------------------------------------------------------------------------------- 1 | Chalice Dojo 2 | ============ 3 | 4 | 1. Create IAM Role with policies AmazonAPIGatewayAdministrator and 5 | AWSLambdaFullAccess. 6 | 2. Add role settings to chalice configuration file ".chalice/config.json" as 7 | follows: 8 | ``` 9 | ... 10 | "manage_iam_role":false, 11 | "iam_role_arn":"arn:aws:iam::018128475095:role/tank-dev" 12 | ... 13 | ``` 14 | 3. Configure AWS credentials key/secret: 15 | ``` 16 | $ mkdir ~/.aws 17 | $ cat >> ~/.aws/config 18 | [default] 19 | aws_access_key_id=YOUR_ACCESS_KEY_HERE 20 | aws_secret_access_key=YOUR_SECRET_ACCESS_KEY 21 | region=YOUR_REGION (such as us-west-2, us-west-1, etc) 22 | ``` 23 | 4. Install chalice and create new project, "hello world" by default. 24 | ``` 25 | $ pip install chalice 26 | $ chalice new-project tank && cd tank 27 | ``` 28 | 5. Deploy "hello world". Manually check (Commit #1). 29 | ``` 30 | $ chalice deploy 31 | ``` 32 | 6. Random tank commander. Manually check. 33 | 7. Manually check as follows: 34 | ``` 35 | curl -XPOST https://o3pl7kr1sd.execute-api.eu-central-1.amazonaws.com/api/command 36 | ``` 37 | 8. Check testing the tank API and fix with Cross-Origin Resource Sharing 38 | (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Commit #2). 39 | 9. Adding requirements. Add required packages into 40 | "tank/requirements.txt" as follows (Commit #3): 41 | ``` 42 | pytz==2017.3 43 | ``` 44 | 10. Be creative and get lucky in the Dojo's Tank Wars League!! 45 | 46 | ## Tank Wars ## 47 | 48 | * API Docs: https://tankwars.serverless.camp/pages/api.html. -------------------------------------------------------------------------------- /chalice/requirements.txt: -------------------------------------------------------------------------------- 1 | botocore==1.8.14 2 | chalice==1.1.0 3 | click==6.6 4 | docutils==0.14 5 | jmespath==0.9.3 6 | python-dateutil==2.6.1 7 | six==1.11.0 8 | typing==3.5.3.0 9 | -------------------------------------------------------------------------------- /chalice/tank/.gitignore: -------------------------------------------------------------------------------- 1 | .chalice 2 | -------------------------------------------------------------------------------- /chalice/tank/app.py: -------------------------------------------------------------------------------- 1 | import random 2 | from pytz import timezone 3 | 4 | from chalice import Chalice 5 | 6 | app = Chalice(app_name='tank') 7 | 8 | app.debug = True 9 | 10 | 11 | @app.route('/') 12 | def index(): 13 | return {'hello': 'world'} 14 | 15 | 16 | @app.route('/info', cors=True) 17 | def info(): 18 | return { 19 | 'name': 'My Tank {!s}'.format(str(timezone('Europe/Amsterdam').zone)), 20 | 'owner': 'PyBCN Dojo' 21 | } 22 | 23 | 24 | @app.route('/command', methods=['POST'], cors=True) 25 | def command(): 26 | commands = [ 27 | 'turn-left', 28 | 'turn-right', 29 | 'forward', 30 | 'reverse', 31 | 'fire', 32 | 'pass' 33 | ] 34 | return { 35 | 'command': random.choice(commands) 36 | } 37 | -------------------------------------------------------------------------------- /chalice/tank/requirements.txt: -------------------------------------------------------------------------------- 1 | pytz==2017.3 2 | -------------------------------------------------------------------------------- /coins/README.md: -------------------------------------------------------------------------------- 1 | Kata base 2 | ========= 3 | 4 | Ésta es la kata base para los dojos. 5 | -------------------------------------------------------------------------------- /coins/kata.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/coins/kata.py -------------------------------------------------------------------------------- /coins/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestKata(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /coins/tests_EP2015.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | MONEDERO_EUR = {'5c': 2, '10c': 2, '20c': 2, '50c': 2, '1e': 2, '2e': 1} 6 | MONEDERO_USD = {'5c': 4, '10c': 3, '25c': 2, '50c': 5, '1d': 2} 7 | 8 | def coins(cantidad, currency): 9 | assert type(cantidad) is float 10 | monedero = MONEDERO_EUR if currency == 'EUR' else MONEDERO_USD 11 | cantidad = int(cantidad * 100) 12 | monedas = { 13 | 'EUR': [200, 100, 50, 20, 10, 5], 14 | 'USD': [100, 50, 25, 10, 5]} 15 | denominaciones = { 16 | 'EUR': ['2e', '1e', '50c', '20c', '10c', '5c'], 17 | 'USD': ['1d', '50c', '25c', '10c', '5c']} 18 | resultado = {} 19 | for m, d in zip(monedas[currency], denominaciones[currency]): 20 | cuenta, cantidad = divmod(cantidad, m) 21 | if cuenta != 0: 22 | if cuenta <= monedero[d]: 23 | resultado[d] = cuenta 24 | else: 25 | resultado[d] = monedero[d] 26 | cantidad += (cuenta - monedero[d]) * m 27 | return resultado 28 | 29 | class TestCoins(unittest.TestCase): 30 | 31 | currency = 'EUR' 32 | resultados_mvp = [ 33 | {'5c': 1, '20c': 1}, 34 | {'5c': 1, '10c': 1, '20c': 1, '2e': 1}, 35 | {'1e': 1, '2e': 1}, 36 | {'10c': 1, '20c': 2, '50c': 2, '1e': 2, '2e': 1}] 37 | cantidades_mvp = [ 38 | 0.25, 39 | 2.35, 40 | 3.00, 41 | 5.50] 42 | 43 | def test_00_5_centimos(self): 44 | resultado = coins(0.05, self.currency) 45 | self.assertEqual({'5c': 1}, resultado, "5 centimos en una moneda") 46 | 47 | def test_01_50_centimos(self): 48 | resultado = coins(0.5, self.currency) 49 | self.assertEqual({'50c': 1}, resultado, "50 centimos en una moneda") 50 | 51 | def test_02_mvp(self): 52 | for i, cantidad in enumerate(self.cantidades_mvp): 53 | resultado = coins(cantidad, self.currency) 54 | self.assertEqual(self.resultados_mvp[i], resultado, "{} centimos".format(cantidad)) 55 | 56 | class TestCoinsUSD(TestCoins): 57 | 58 | currency = 'USD' 59 | resultados_mvp = [ 60 | {'25c': 1}, 61 | {'10c': 1, '25c': 1, '1d': 2}, 62 | {'50c': 2, '1d': 2}, 63 | {'5c': 4, '10c': 3, '25c': 2, '50c': 5, '1d': 2}] 64 | cantidades_mvp = [ 65 | 0.25, 66 | 2.35, 67 | 3.00, 68 | 5.50] 69 | 70 | if __name__ == '__main__': 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /concurrency/images/clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/concurrency/images/clock.jpg -------------------------------------------------------------------------------- /concurrency/images/cpu_i7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/concurrency/images/cpu_i7.jpg -------------------------------------------------------------------------------- /concurrency/images/io.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/concurrency/images/io.jpg -------------------------------------------------------------------------------- /concurrency/images/man.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/concurrency/images/man.jpg -------------------------------------------------------------------------------- /cracking_classical_ciphers/caesar_cipher.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Caesar cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #1: Encode with key=`23`:\n", 11 | " `HELLO WORLD!`\n", 12 | "- ### Task #2: Decode:\n", 13 | " `OVSH TBUKV`" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "### Hints:" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "from string import ascii_uppercase\n", 30 | "\n", 31 | "\n", 32 | "print('English alphabet:',ascii_uppercase)\n", 33 | "print('English alphabet length:', len(ascii_uppercase))" 34 | ] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3", 40 | "language": "python", 41 | "name": "python3" 42 | }, 43 | "language_info": { 44 | "codemirror_mode": { 45 | "name": "ipython", 46 | "version": 3 47 | }, 48 | "file_extension": ".py", 49 | "mimetype": "text/x-python", 50 | "name": "python", 51 | "nbconvert_exporter": "python", 52 | "pygments_lexer": "ipython3", 53 | "version": "3.7.0" 54 | } 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 2 58 | } 59 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/caesar_cipher_solution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Caesar cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #1: Encode with key=`23`:\n", 11 | " `HELLO WORLD!`\n", 12 | "- ### Task #2: Decode:\n", 13 | " `OVSH TBUKV`" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "### Task #1 Solution" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "from string import ascii_uppercase as alphabet\n", 30 | "\n", 31 | "\n", 32 | "plain_text = 'HELLO WORLD!'\n", 33 | "key = 23\n", 34 | "\n", 35 | "encrypted_text = ''\n", 36 | "for symbol in plain_text:\n", 37 | " if symbol in alphabet:\n", 38 | " symbol_index = alphabet.index(symbol)\n", 39 | " encrypted_symbol_index = (symbol_index + key) % 26\n", 40 | " encrypted_symbol = alphabet[encrypted_symbol_index]\n", 41 | " else:\n", 42 | " encrypted_symbol = symbol\n", 43 | " encrypted_text += encrypted_symbol\n", 44 | "\n", 45 | "print(encrypted_text)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "### Task #2 Solution" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "cipher_text = 'OVSH TBUKV'\n", 62 | "\n", 63 | "\n", 64 | "def caesar_decrypt(cipher_text, key):\n", 65 | " plain_text = ''\n", 66 | " for symbol in cipher_text:\n", 67 | " if symbol in alphabet:\n", 68 | " symbol_index = alphabet.index(symbol)\n", 69 | " decrypted_symbol_index = (symbol_index - key) % 26\n", 70 | " decrypted_symbol = alphabet[decrypted_symbol_index]\n", 71 | " else:\n", 72 | " decrypted_symbol = symbol\n", 73 | " plain_text += decrypted_symbol\n", 74 | " return plain_text\n", 75 | "\n", 76 | "\n", 77 | "# Brute-force attack\n", 78 | "for key in range(26):\n", 79 | " plain_text = caesar_decrypt(cipher_text, key)\n", 80 | " print(plain_text, ' key:', key)" 81 | ] 82 | } 83 | ], 84 | "metadata": { 85 | "kernelspec": { 86 | "display_name": "Python 3", 87 | "language": "python", 88 | "name": "python3" 89 | }, 90 | "language_info": { 91 | "codemirror_mode": { 92 | "name": "ipython", 93 | "version": 3 94 | }, 95 | "file_extension": ".py", 96 | "mimetype": "text/x-python", 97 | "name": "python", 98 | "nbconvert_exporter": "python", 99 | "pygments_lexer": "ipython3", 100 | "version": "3.7.0" 101 | } 102 | }, 103 | "nbformat": 4, 104 | "nbformat_minor": 2 105 | } 106 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/img/caesar_cipher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/cracking_classical_ciphers/img/caesar_cipher.png -------------------------------------------------------------------------------- /cracking_classical_ciphers/img/scytale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/cracking_classical_ciphers/img/scytale.png -------------------------------------------------------------------------------- /cracking_classical_ciphers/img/substitution_cipher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/cracking_classical_ciphers/img/substitution_cipher.png -------------------------------------------------------------------------------- /cracking_classical_ciphers/img/vigenere.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/cracking_classical_ciphers/img/vigenere.jpg -------------------------------------------------------------------------------- /cracking_classical_ciphers/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter==1.0.0 2 | plotly==3.2.0 -------------------------------------------------------------------------------- /cracking_classical_ciphers/scytale.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Scytale" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "\n", 15 | "\n", 16 | "- ### Task #3: Encrypt with the key `6` this message:\n", 17 | " `YESTERDAY ALL MY TROUBLES SEEMED SO FAR AWAY`\n", 18 | "\n", 19 | " |1|2|3|4|5|6|7\n", 20 | "-|-|-|-|-|-|-|-\n", 21 | "1|Y|E|S|T|E|R|D\n", 22 | "2|A|Y|A|L|L|M|\n", 23 | "3|Y|T|R|O|U|B|\n", 24 | "4|L|E|S|S|E|E|\n", 25 | "5|M|E|D|S|O|F|\n", 26 | "6|A|R|A|W|A|Y|\n", 27 | "\n", 28 | " |1|2|3|4|5|6|7\n", 29 | "-|-|-|-|-|-|-|-\n", 30 | "1|Y|E|S|T|E|R|D\n", 31 | "2|A|Y|A|L|L|M|Y\n", 32 | "3|T|R|O|U|B|L|E\n", 33 | "4|S|S|E|E|M|E|D\n", 34 | "5|S|O|F|A|R|A|W\n", 35 | "6|A|Y|·|·|·|·|·\n", 36 | "\n", 37 | "Expected result:\n", 38 | "`YATSSAEYRSOYSAOEFTLUEAELBMRRMLEADYEDW`" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "- ### Task #4: Decrypt the scytale you have on the table or download it here: https://goo.gl/7Rt87q" 46 | ] 47 | } 48 | ], 49 | "metadata": { 50 | "kernelspec": { 51 | "display_name": "Python 3", 52 | "language": "python", 53 | "name": "python3" 54 | }, 55 | "language_info": { 56 | "codemirror_mode": { 57 | "name": "ipython", 58 | "version": 3 59 | }, 60 | "file_extension": ".py", 61 | "mimetype": "text/x-python", 62 | "name": "python", 63 | "nbconvert_exporter": "python", 64 | "pygments_lexer": "ipython3", 65 | "version": "3.7.0" 66 | } 67 | }, 68 | "nbformat": 4, 69 | "nbformat_minor": 2 70 | } 71 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/scytale_solution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Scytale\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #3: Encrypt with the key `6` this message:\n", 11 | " `YESTERDAY ALL MY TROUBLES SEEMED SO FAR AWAY`" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | " |1|2|3|4|5|6|7\n", 19 | "-|-|-|-|-|-|-|-\n", 20 | "1|Y|E|S|T|E|R|D\n", 21 | "2|A|Y|A|L|L|M|\n", 22 | "3|Y|T|R|O|U|B|\n", 23 | "4|L|E|S|S|E|E|\n", 24 | "5|M|E|D|S|O|F|\n", 25 | "6|A|R|A|W|A|Y|" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "### Task #3 Solution" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "KEY = 13\n", 42 | "plain_text = 'YESTERDAY ALL MY TROUBLES SEEMED SO FAR AWAY'\n", 43 | "plain_text = plain_text.replace(' ', '')\n", 44 | "\n", 45 | "print(plain_text)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37\n", 53 | "-|-|-|-|-|-|-|-|-|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--\n", 54 | "Y|E|S|T|E|R|D|A|Y|A |L |L |M |Y |T |R |O |U |B |L |E |S |S |E |E |M |E |D |S |O |F |A |R |A |W |A |Y" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "# fill plain text with empty spaces\n", 64 | "from math import ceil\n", 65 | "\n", 66 | "str_len = ceil(len(plain_text) / KEY)\n", 67 | "spaces = KEY * str_len - len(plain_text)\n", 68 | "for i in range(spaces):\n", 69 | " if i == 0:\n", 70 | " plain_text += '.'\n", 71 | " continue\n", 72 | " insert_index = - (str_len * i)\n", 73 | " plain_text = plain_text[:insert_index] + '.' + plain_text[insert_index:]\n", 74 | "\n", 75 | "print(plain_text)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42\n", 83 | "-|-|-|-|-|-|-|-|-|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--\n", 84 | "Y|E|S|T|E|R|D|A|Y|A|L|L|M|.|Y|T|R|O|U|B|.|L|E|S|S|E|E|.|M|E|D|S|O|F|.|A|R|A|W|A|Y|." 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "encrypted_text = ''\n", 94 | "for i in range(str_len):\n", 95 | " encrypted_text += plain_text[i::str_len]\n", 96 | "\n", 97 | "print(encrypted_text)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "encrypted_text = encrypted_text.replace('.', '')\n", 107 | "\n", 108 | "print(encrypted_text)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "### Task #4 Solution" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "encrypted_text = 'YTDLESCNSATPAEEAOWSHEYMOLYSRYVAUAAGE'\n", 125 | "\n", 126 | "\n", 127 | "str_len = ceil(len(encrypted_text) / KEY)\n", 128 | "\n", 129 | "for key in range(2, len(encrypted_text)+1):\n", 130 | " spaces = key * str_len - len(encrypted_text)\n", 131 | " encrypted_text_copy = encrypted_text + '.'*spaces\n", 132 | " plain_text = ''\n", 133 | " for i in range(key):\n", 134 | " plain_text += encrypted_text_copy[i::key]\n", 135 | " plain_text = plain_text.replace('.', '')\n", 136 | " print(plain_text, 'Key:', key)\n" 137 | ] 138 | } 139 | ], 140 | "metadata": { 141 | "kernelspec": { 142 | "display_name": "Python 3", 143 | "language": "python", 144 | "name": "python3" 145 | }, 146 | "language_info": { 147 | "codemirror_mode": { 148 | "name": "ipython", 149 | "version": 3 150 | }, 151 | "file_extension": ".py", 152 | "mimetype": "text/x-python", 153 | "name": "python", 154 | "nbconvert_exporter": "python", 155 | "pygments_lexer": "ipython3", 156 | "version": "3.7.0" 157 | } 158 | }, 159 | "nbformat": 4, 160 | "nbformat_minor": 2 161 | } 162 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/substitution_cipher.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Substitution cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #5: Encode with random.shuffle and random.seed(774):\n", 11 | " `HELLO WORLD`\n", 12 | "- ### Task #6: Decrypt the cipher from here: https://goo.gl/7Rt87q" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "### Task #5 Hints:" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import random\n", 29 | "from string import ascii_uppercase as alphabet\n", 30 | "\n", 31 | "\n", 32 | "random.seed(774)\n", 33 | "\n", 34 | "substitution_alphabet = list(alphabet)\n", 35 | "random.shuffle(substitution_alphabet)\n", 36 | "substitution_alphabet = ''.join(substitution_alphabet)\n", 37 | "\n", 38 | " \n", 39 | "print(alphabet)\n", 40 | "print(substitution_alphabet)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z\n", 48 | "-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-\n", 49 | "X|Y|E|L|Q|G|M|W|T|Z|R|C|K|B|N|V|U|I|H|F|O|P|S|D|A|J" 50 | ] 51 | } 52 | ], 53 | "metadata": { 54 | "kernelspec": { 55 | "display_name": "Python 3", 56 | "language": "python", 57 | "name": "python3" 58 | }, 59 | "language_info": { 60 | "codemirror_mode": { 61 | "name": "ipython", 62 | "version": 3 63 | }, 64 | "file_extension": ".py", 65 | "mimetype": "text/x-python", 66 | "name": "python", 67 | "nbconvert_exporter": "python", 68 | "pygments_lexer": "ipython3", 69 | "version": "3.7.0" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 2 74 | } 75 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/substitution_cipher_solution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Substitution cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #5: Encode with random.shuffle and random.seed(774):\n", 11 | " `HELLO WORLD`" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "### Task #5 Solution" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import random\n", 28 | "from string import ascii_uppercase as alphabet\n", 29 | "\n", 30 | "\n", 31 | "random.seed(774)\n", 32 | "\n", 33 | "substitution_alphabet = list(alphabet)\n", 34 | "random.shuffle(substitution_alphabet)\n", 35 | "substitution_alphabet = ''.join(substitution_alphabet)\n", 36 | "\n", 37 | " \n", 38 | "print(alphabet)\n", 39 | "print(substitution_alphabet)" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "plain_text = 'HELLO WORLD'.replace(' ', '')\n", 49 | "encrypted_text = ''\n", 50 | "\n", 51 | "for symbol in plain_text:\n", 52 | " index = alphabet.index(symbol)\n", 53 | " encrypted_symbol = substitution_alphabet[index]\n", 54 | " encrypted_text += encrypted_symbol\n", 55 | "\n", 56 | "print(encrypted_text)" 57 | ] 58 | } 59 | ], 60 | "metadata": { 61 | "kernelspec": { 62 | "display_name": "Python 3", 63 | "language": "python", 64 | "name": "python3" 65 | }, 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 3 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython3", 76 | "version": "3.7.0" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 2 81 | } 82 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/vigenere_cipher.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Vigenere cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #7: Encode with key=`BCN`:\n", 11 | " `HELLO WORLD`\n", 12 | "- ### Task #8: Decrypt the cipher from here: https://goo.gl/7Rt87q" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "### Example\n", 20 | "key = `MAD` \n", 21 | "plain_text = `HELLO WORLD`" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "1. Find column `H` and find row `M`: `T`\n", 29 | "2. Find column `E` and find row `A`: `E`\n", 30 | "3. Find column `L` and find row `D`: `O`\n", 31 | "---\n", 32 | "4. Find column `L` and find row `M`: `X`\n", 33 | "5. Find column `O` and find row `A`: `O`\n", 34 | "6. Find column `W` and find row `D`: `Z`\n", 35 | "---\n", 36 | "7. Find column `O` and find row `M`: `A`\n", 37 | "8. Find column `R` and find row `A`: `R`\n", 38 | "9. Find column `L` and find row `D`: `O`\n", 39 | "---\n", 40 | "10. Find column `D` and find row `M`: `P`" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "#### Answer: TEOXO ZAROP" 48 | ] 49 | } 50 | ], 51 | "metadata": { 52 | "kernelspec": { 53 | "display_name": "Python 3", 54 | "language": "python", 55 | "name": "python3" 56 | }, 57 | "language_info": { 58 | "codemirror_mode": { 59 | "name": "ipython", 60 | "version": 3 61 | }, 62 | "file_extension": ".py", 63 | "mimetype": "text/x-python", 64 | "name": "python", 65 | "nbconvert_exporter": "python", 66 | "pygments_lexer": "ipython3", 67 | "version": "3.7.0" 68 | } 69 | }, 70 | "nbformat": 4, 71 | "nbformat_minor": 2 72 | } 73 | -------------------------------------------------------------------------------- /cracking_classical_ciphers/vigenere_cipher_solution_1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Vigenere cipher\n", 8 | "\n", 9 | "\n", 10 | "- ### Task #7: Encode with key=`BCN`:\n", 11 | " `HELLO WORLD`" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "### Solution" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "key = 'BCN'\n", 28 | "plain_text = 'HELLO WORLD'.replace(' ', '')" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 2, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "from string import ascii_uppercase as alphabet\n", 38 | "from pprint import pprint\n", 39 | "\n", 40 | "\n", 41 | "vigerene_matrix = []\n", 42 | "for i in range(len(alphabet)):\n", 43 | " row = list(alphabet[i:] + alphabet[:i])\n", 44 | " vigerene_matrix.append(row)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "IGYMQJPTYE\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "from itertools import cycle\n", 62 | "\n", 63 | "\n", 64 | "encrypted_text = ''\n", 65 | "\n", 66 | "for letter, key_letter in zip(plain_text, cycle(key)):\n", 67 | " column = alphabet.index(letter)\n", 68 | " row = alphabet.index(key_letter)\n", 69 | " encrypted_text += vigerene_matrix[column][row]\n", 70 | "\n", 71 | "print(encrypted_text)" 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "kernelspec": { 77 | "display_name": "Python 3", 78 | "language": "python", 79 | "name": "python3" 80 | }, 81 | "language_info": { 82 | "codemirror_mode": { 83 | "name": "ipython", 84 | "version": 3 85 | }, 86 | "file_extension": ".py", 87 | "mimetype": "text/x-python", 88 | "name": "python", 89 | "nbconvert_exporter": "python", 90 | "pygments_lexer": "ipython3", 91 | "version": "3.7.0" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 2 96 | } 97 | -------------------------------------------------------------------------------- /doc/Mocking.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:f06f04b7e83dc72d9dea1261c6bc2046f51c166272a4d356af32774eae0ae6bd" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "heading", 13 | "level": 1, 14 | "metadata": {}, 15 | "source": [ 16 | "Testing: Mocking" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "Mocking is a testing technique in which we create a faked element which will behave or look like another real one, so the tested element can work as expected. A very good example would be the role of the crash test dummies in car design tests." 24 | ] 25 | }, 26 | { 27 | "cell_type": "heading", 28 | "level": 2, 29 | "metadata": {}, 30 | "source": [ 31 | "Why to mock?" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "In testing, mocking is used to simulate the behavior of a real complex element which is impractical or impossible to incorporate in the test. If an element presents any of the following characteristics, it would be useful to use a mock:\n", 39 | "\n", 40 | " * It supplies non-deterministic values, like times, temperature, etc.\n", 41 | " * It can present states difficult to reproduce, like delays, disconnections, etc.\n", 42 | " * It requires an initialization which might slow the test run.\n", 43 | " * Its interfaces are not know, existing, or stable.\n", 44 | " * It would need to include information or methods exclusively for testing purposes, like measurement, or counting." 45 | ] 46 | }, 47 | { 48 | "cell_type": "heading", 49 | "level": 2, 50 | "metadata": {}, 51 | "source": [ 52 | "How to mock?" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Mock objects have the same interface of the objects they mimic, so the objects using them are unaware they're not the real object. Most mocking frameworks allow to specify which, and in what order, methods will be invoked, with which parameters, as well as the values that should be returned." 60 | ] 61 | }, 62 | { 63 | "cell_type": "heading", 64 | "level": 3, 65 | "metadata": {}, 66 | "source": [ 67 | "Kinds of mocks" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "There are some terms to refer to mocks that usually point to subtile differences:\n", 75 | "\n", 76 | " * **Double**: It is the generic name for a pretend object used in place of a real one, for testing purposes.\n", 77 | " * **Dummy**: It's an object to be passed around but never actually used.\n", 78 | " * **Fake**: These objects have working implementations, but they're taking shortcuts from the real objects.\n", 79 | " * **Stub**: In that case, the object provides the interface for specific behavior expected, and corresponding canned responses.\n", 80 | " * **Mock**: This kind of object is used for setting expectations, not only the interface and its responses. These expectations may intervene to determine test's failure or success.\n", 81 | "\n", 82 | "Some of these terms can be twisted, depending on the author, like using the term fake, instead of stub. So these are the widest set and definitions." 83 | ] 84 | }, 85 | { 86 | "cell_type": "heading", 87 | "level": 2, 88 | "metadata": {}, 89 | "source": [ 90 | "Example" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "In this example, we're working in a selling system which communicates with the warehouse system, to determine if there are enough quantity of the product." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "collapsed": false, 103 | "input": [ 104 | "%%file tests.py\n", 105 | "import unittest\n", 106 | "import mock\n", 107 | "import pos\n", 108 | "\n", 109 | "class SellingSystemTest(unittest.TestCase):\n", 110 | " def setUp(self):\n", 111 | " self.warehouse = mock.Mock()\n", 112 | " self.point_of_sale = pos.POS(warehouse = self.warehouse)\n", 113 | "\n", 114 | " def test_product_existance(self):\n", 115 | " self.warehouse.check_product.return_value = True\n", 116 | " self.point_of_sale.add('Product A', 25)\n", 117 | " self.warehouse.check_product.assert_called_with('Product A', 25)\n", 118 | "\n", 119 | "if __name__ == '__main__':\n", 120 | " unittest.main()" 121 | ], 122 | "language": "python", 123 | "metadata": {}, 124 | "outputs": [] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "collapsed": false, 129 | "input": [ 130 | "%%file pos.py\n", 131 | "class POS():\n", 132 | " def __init__(self, warehouse):\n", 133 | " pass\n", 134 | "\n", 135 | " def add(self, name, quantity):\n", 136 | " pass" 137 | ], 138 | "language": "python", 139 | "metadata": {}, 140 | "outputs": [] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "collapsed": false, 145 | "input": [ 146 | "!python tests.py" 147 | ], 148 | "language": "python", 149 | "metadata": {}, 150 | "outputs": [] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "collapsed": false, 155 | "input": [ 156 | "%%file pos.py\n", 157 | "class POS():\n", 158 | " def __init__(self, warehouse):\n", 159 | " self.warehouse = warehouse\n", 160 | "\n", 161 | " def add(self, name, quantity):\n", 162 | " self.warehouse.check_product(name, quantity)" 163 | ], 164 | "language": "python", 165 | "metadata": {}, 166 | "outputs": [] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "collapsed": false, 171 | "input": [ 172 | "!python tests.py" 173 | ], 174 | "language": "python", 175 | "metadata": {}, 176 | "outputs": [] 177 | } 178 | ], 179 | "metadata": {} 180 | } 181 | ] 182 | } -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.3 2 | MarkupSafe==0.23 3 | backports.ssl-match-hostname==3.4.0.2 4 | certifi==14.05.14 5 | gnureadline==6.3.3 6 | ipython==2.3.1 7 | mock==1.0.1 8 | pyzmq==14.4.1 9 | tornado==4.0.2 10 | wsgiref==0.1.2 11 | -------------------------------------------------------------------------------- /dojos.css: -------------------------------------------------------------------------------- 1 | section#sec-title-slide { 2 | height: 100%; 3 | vertical-align: text-bottom; 4 | } 5 | section#sec-title-slide h1 { 6 | margin-top: 50%; 7 | color: orange; 8 | } 9 | section p { 10 | text-align: left; 11 | } 12 | 13 | .figure p { 14 | text-align: center; 15 | } 16 | 17 | section pre { 18 | background-color: white; 19 | color: black; 20 | } 21 | -------------------------------------------------------------------------------- /factor-out/.gitignore: -------------------------------------------------------------------------------- 1 | .cache/** 2 | .ipynb_checkpoints/** 3 | __pycache__/** -------------------------------------------------------------------------------- /factor-out/football.dat: -------------------------------------------------------------------------------- 1 | Team P W L D F A Pts 2 | 1. Arsenal 38 26 9 3 79 - 36 87 3 | 2. Liverpool 38 24 8 6 67 - 30 80 4 | 3. Manchester_U 38 24 5 9 87 - 45 77 5 | 4. Newcastle 38 21 8 9 74 - 52 71 6 | 5. Leeds 38 18 12 8 53 - 37 66 7 | 6. Chelsea 38 17 13 8 66 - 38 64 8 | 7. West_Ham 38 15 8 15 48 - 57 53 9 | 8. Aston_Villa 38 12 14 12 46 - 47 50 10 | 9. Tottenham 38 14 8 16 49 - 53 50 11 | 10. Blackburn 38 12 10 16 55 - 51 46 12 | 11. Southampton 38 12 9 17 46 - 54 45 13 | 12. Middlesbrough 38 12 9 17 35 - 47 45 14 | 13. Fulham 38 10 14 14 36 - 44 44 15 | 14. Charlton 38 10 14 14 38 - 49 44 16 | 15. Everton 38 11 10 17 45 - 57 43 17 | 16. Bolton 38 9 13 16 44 - 62 40 18 | 17. Sunderland 38 10 10 18 29 - 51 40 19 | ------------------------------------------------------- 20 | 18. Ipswich 38 9 9 20 41 - 64 36 21 | 19. Derby 38 8 6 24 33 - 63 30 22 | 20. Leicester 38 5 13 20 30 - 64 28 23 | -------------------------------------------------------------------------------- /factor-out/weather.dat: -------------------------------------------------------------------------------- 1 | Dy MxT MnT AvT HDDay AvDP 1HrP TPcpn WxType PDir AvSp Dir MxS SkyC MxR MnR AvSLP 2 | 3 | 1 88 59 74 53.8 0.00 F 280 9.6 270 17 1.6 93 23 1004.5 4 | 2 79 63 71 46.5 0.00 330 8.7 340 23 3.3 70 28 1004.5 5 | 3 77 55 66 39.6 0.00 350 5.0 350 9 2.8 59 24 1016.8 6 | 4 77 59 68 51.1 0.00 110 9.1 130 12 8.6 62 40 1021.1 7 | 5 90 66 78 68.3 0.00 TFH 220 8.3 260 12 6.9 84 55 1014.4 8 | 6 81 61 71 63.7 0.00 RFH 030 6.2 030 13 9.7 93 60 1012.7 9 | 7 73 57 65 53.0 0.00 RF 050 9.5 050 17 5.3 90 48 1021.8 10 | 8 75 54 65 50.0 0.00 FH 160 4.2 150 10 2.6 93 41 1026.3 11 | 9 86 32* 59 6 61.5 0.00 240 7.6 220 12 6.0 78 46 1018.6 12 | 10 84 64 74 57.5 0.00 F 210 6.6 050 9 3.4 84 40 1019.0 13 | 11 91 59 75 66.3 0.00 H 250 7.1 230 12 2.5 93 45 1012.6 14 | 12 88 73 81 68.7 0.00 RTH 250 8.1 270 21 7.9 94 51 1007.0 15 | 13 70 59 65 55.0 0.00 H 150 3.0 150 8 10.0 83 59 1012.6 16 | 14 61 59 60 5 55.9 0.00 RF 060 6.7 080 9 10.0 93 87 1008.6 17 | 15 64 55 60 5 54.9 0.00 F 040 4.3 200 7 9.6 96 70 1006.1 18 | 16 79 59 69 56.7 0.00 F 250 7.6 240 21 7.8 87 44 1007.0 19 | 17 81 57 69 51.7 0.00 T 260 9.1 270 29* 5.2 90 34 1012.5 20 | 18 82 52 67 52.6 0.00 230 4.0 190 12 5.0 93 34 1021.3 21 | 19 81 61 71 58.9 0.00 H 250 5.2 230 12 5.3 87 44 1028.5 22 | 20 84 57 71 58.9 0.00 FH 150 6.3 160 13 3.6 90 43 1032.5 23 | 21 86 59 73 57.7 0.00 F 240 6.1 250 12 1.0 87 35 1030.7 24 | 22 90 64 77 61.1 0.00 H 250 6.4 230 9 0.2 78 38 1026.4 25 | 23 90 68 79 63.1 0.00 H 240 8.3 230 12 0.2 68 42 1021.3 26 | 24 90 77 84 67.5 0.00 H 350 8.5 010 14 6.9 74 48 1018.2 27 | 25 90 72 81 61.3 0.00 190 4.9 230 9 5.6 81 29 1019.6 28 | 26 97* 64 81 70.4 0.00 H 050 5.1 200 12 4.0 107 45 1014.9 29 | 27 91 72 82 69.7 0.00 RTH 250 12.1 230 17 7.1 90 47 1009.0 30 | 28 84 68 76 65.6 0.00 RTFH 280 7.6 340 16 7.0 100 51 1011.0 31 | 29 88 66 77 59.7 0.00 040 5.4 020 9 5.3 84 33 1020.6 32 | 30 90 45 68 63.6 0.00 H 240 6.0 220 17 4.8 200 41 1022.7 33 | mo 82.9 60.5 71.7 16 58.8 0.00 6.9 5.3 34 | -------------------------------------------------------------------------------- /firebase-push-notifications/.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | **/.vscode 3 | **/.idea 4 | 5 | # Python 6 | **/*.pyc 7 | __pycache__ 8 | bin 9 | include 10 | lib 11 | .Python 12 | pip-selfcheck.json 13 | -------------------------------------------------------------------------------- /firebase-push-notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/firebase-push-notifications/__init__.py -------------------------------------------------------------------------------- /firebase-push-notifications/api.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify, request 2 | from sqlalchemy.exc import SQLAlchemyError 3 | from models import db, User 4 | 5 | api = Blueprint('api', __name__) 6 | 7 | 8 | @api.route('/save-token', methods=['POST']) 9 | def save_token(): 10 | token = request.json.get('token') 11 | user = User(token=token) 12 | 13 | try: 14 | db.session.add(user) 15 | db.session.commit() 16 | except SQLAlchemyError: 17 | return jsonify({'error': SQLAlchemyError.__name__}) 18 | 19 | return jsonify({'token': token}) 20 | -------------------------------------------------------------------------------- /firebase-push-notifications/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | 4 | def create_app(): 5 | app = Flask(__name__) 6 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///notifications.db' 7 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 8 | 9 | from api import api 10 | from views import views 11 | app.register_blueprint(api) 12 | app.register_blueprint(views) 13 | 14 | from models import db 15 | db.init_app(app) 16 | 17 | return app 18 | 19 | 20 | if __name__ == '__main__': 21 | create_app().run(debug=True) 22 | -------------------------------------------------------------------------------- /firebase-push-notifications/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from flask_sqlalchemy import SQLAlchemy 4 | 5 | db = SQLAlchemy() 6 | Base = declarative_base() 7 | 8 | class User(Base): 9 | __tablename__ = "user" 10 | id = Column(Integer, primary_key=True) 11 | token = Column(String(80), unique=True, nullable=False) 12 | 13 | 14 | def get_list(): 15 | return User.query.all() 16 | -------------------------------------------------------------------------------- /firebase-push-notifications/notifications.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/firebase-push-notifications/notifications.db -------------------------------------------------------------------------------- /firebase-push-notifications/notifier.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from models import User 3 | from sqlalchemy import create_engine, orm 4 | 5 | fcm_url = "https://fcm.googleapis.com/fcm/send" 6 | headers = { 7 | "Content-Type": "application/json", 8 | "Authorization": "key=your-key-goes-here" 9 | } 10 | data = { 11 | "notification": { 12 | "title": "Push notification test", 13 | "body": "Test from Python", 14 | "click_action": "http://localhost:5000/list", 15 | "icon": "https://www.python.org/static/favicon.ico" 16 | }, 17 | } 18 | 19 | 20 | def notify_all(): 21 | engine = create_engine('sqlite:///notifications.db') 22 | session = orm.sessionmaker(bind=engine)() 23 | 24 | for record in session.query(User).all(): 25 | data["to"] = record.token 26 | 27 | req = requests.post(fcm_url, json=data, headers=headers) 28 | print(req.json()) 29 | 30 | 31 | if __name__ == "__main__": 32 | notify_all() 33 | -------------------------------------------------------------------------------- /firebase-push-notifications/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-sqlalchemy 3 | requests 4 | -------------------------------------------------------------------------------- /firebase-push-notifications/slides.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme : "black" 3 | transition: "zoom" 4 | highlightTheme: "atom-one-dark" 5 | logoImg: "https://raw.githubusercontent.com/evilz/vscode-reveal/master/images/logo-v2.png" 6 | slideNumber: true 7 | --- 8 | 9 |

SSID: Punt Multimedia Aula 2_4Ghz

10 |

github: https://github.com/BCNDojos/pyDojos.git

11 |

branch: start

12 | 13 | --- 14 | 15 | ## April PyBCN Practice Sessions 16 | 17 | pythonanywhere.com & firebase push notifications 18 | 19 | 20 | Ricardo Martinez [@lordrip](http://twitter.com/lordrip) 21 | 22 | --- 23 | 24 | ## Who am I? 25 | 26 | I'm Ricardo Martinez, I'm a front-end developer (WTF?) that loves JS & Python. 27 | Sharing things keeps a little bit more in touch with the language and the community. 28 | I'm using python for automate task and craft tooling for myself (like the stress-loader for servers) 29 | 30 | --- 31 | 32 | ### First things first 33 | 34 | -- 35 | 36 | ### What is firebase? 37 | 38 |

Firebase is set of cloud based services that Google offers with a free tier for testing and personal projects

39 |

Today we'll try one of those services, Firebase Messaging. It is a service that allows us to send and receive push notifications on mobiles and web

40 | 41 | -- 42 | 43 | ### What is pythonanywhere.com? 44 | 45 |

pythonanywhere.com is a service that allows us to use python in the cloud; The scope of this service ranges from bash and python terminals, web applications with Django, Flask and more, an instance of MySQL to save data and also allows you to specify tasks that will be executed at certain times.

46 |

Luckily for us, they also have a free level to do personal projects.

47 | 48 | --- 49 | 50 | ### What are we gonna build? 51 | 52 | 53 | 54 | -- 55 | 56 |

Lets build a simple app with Flask that will receive a web token and store it in a sqlite database and later send push notifications

57 | 58 | --- 59 | 60 | ### What are we gonna need? 61 | 62 |
    63 |
  • Flask app
  • 64 |
  • Notifier
  • 65 |
  • Serve our app
  • 66 |
  • Schedule our notifier
  • 67 |
68 | 69 | --- 70 | 71 | ### Flask app 72 | 73 |
    74 |
  • Serve static code (firebase.js, service worker and templates)
  • 75 |
  • Provide an endpoint for submit the token
  • 76 |
77 | 78 | -- 79 | 80 | ## Simple Flask app 81 | 82 |

 83 | from flask import Flask
 84 | 
 85 | app = Flask(__name__)
 86 | 
 87 | @app.route('/')
 88 | def home():
 89 |     return "Hello from Flask"
 90 | 
 91 | app.run()
 92 | 
93 | 94 | -- 95 | 96 | ## Render a template 97 | 98 |

 99 | from flask import Flask, render_template
100 | 
101 | app = Flask(__name__)
102 | 
103 | @app.route('/')
104 | def home():
105 |     return render_template('home01.html')
106 | 
107 | app.run()
108 | 
109 | 110 | -- 111 | 112 | ## Defining a route 113 | 114 |

115 | from flask import Flask, render_template
116 | 
117 | app = Flask(__name__)
118 | 
119 | @app.route('/post', methods=['POST'])
120 | def post_message():
121 |     return jsonify({ 'message': 'hi there form a POST call' })
122 | 
123 | app.run()
124 | 
125 | 126 | --- 127 | 128 | ### Notifier.py 129 | 130 |
    131 |
  • A script that will be triggering push notifications to all users in our database
  • 132 |
133 | 134 | -- 135 | 136 | ## Notifier 137 | 138 |

139 | import requests
140 | from models import User
141 | from sqlalchemy import create_engine, orm
142 | 
143 | def notify_all():
144 |     engine = create_engine('sqlite:///notifications.db')
145 |     session = orm.sessionmaker(bind=engine)()
146 | 
147 |     for record in session.query(User).all():
148 |         data["to"] = record.token
149 | 
150 |         req = requests.post(fcm_url, json=data, headers=headers)
151 |         print(req.json())
152 | 
153 | 
154 | if __name__ == "__main__":
155 |     notify_all()
156 | 
157 | 158 | --- 159 | 160 | ## Pythonanywhere 161 | 162 |
    163 |
  • Generate database
  • 164 |
  • Serve our web app
  • 165 |
  • Schedule our notifier.py
  • 166 |
167 | 168 | -- 169 | 170 | ### Generate database 171 | 172 |

173 | >>> import models
174 | >>> from sqlalchemy import create_engine
175 | >>> engine = create_engine('sqlite:///notifications.db')
176 | >>> models.Base.metadata.create_all(engine)
177 | 
178 | -------------------------------------------------------------------------------- /firebase-push-notifications/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .frame { 2 | padding: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /firebase-push-notifications/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/firebase-push-notifications/static/favicon.ico -------------------------------------------------------------------------------- /firebase-push-notifications/static/js/firebase-messaging-sw.js: -------------------------------------------------------------------------------- 1 | // Give the service worker access to Firebase Messaging. 2 | // Note that you can only use Firebase Messaging here, other Firebase libraries 3 | // are not available in the service worker. 4 | importScripts('https://www.gstatic.com/firebasejs/5.9.2/firebase-app.js'); 5 | importScripts('https://www.gstatic.com/firebasejs/5.9.2/firebase-messaging.js'); 6 | 7 | // Initialize the Firebase app in the service worker by passing in the 8 | // messagingSenderId. 9 | firebase.initializeApp({ 10 | 'messagingSenderId': 'your-token-goes-here' 11 | }); 12 | 13 | // Retrieve an instance of Firebase Messaging so that it can handle background 14 | // messages. 15 | const messaging = firebase.messaging(); 16 | -------------------------------------------------------------------------------- /firebase-push-notifications/static/js/index.js: -------------------------------------------------------------------------------- 1 | // Initialize Firebase 2 | const config = { 3 | apiKey: "your-token-goes-here", 4 | authDomain: "python-april-2019.firebaseapp.com", 5 | databaseURL: "https://python-april-2019.firebaseio.com", 6 | projectId: "python-april-2019", 7 | storageBucket: "python-april-2019.appspot.com", 8 | messagingSenderId: "your-token-goes-here" 9 | }; 10 | firebase.initializeApp(config); 11 | 12 | // Retrieve Firebase Messaging object. 13 | const messaging = firebase.messaging(); 14 | 15 | // Register Firebase Service Worker 16 | if ("serviceWorker" in navigator) { 17 | window.addEventListener("load", () => { 18 | navigator.serviceWorker 19 | .register("/static/js/firebase-messaging-sw.js", { 20 | scope: "/static/js/firebase-cloud-messaging-push-scope" 21 | }) 22 | .then(registration => { 23 | messaging.useServiceWorker(registration); 24 | }); 25 | }); 26 | } else { 27 | console.error("Your browser doesn't support ServiceWorkers"); 28 | } 29 | 30 | // Get current token 31 | const getToken = async () => { 32 | let token; 33 | 34 | try { 35 | // Get Instance ID token. Initially this makes a network call, once retrieved 36 | // subsequent calls to getToken will return from cache. 37 | token = await messaging.getToken(); 38 | if (!token) { 39 | // Show permission request. 40 | await messaging.requestPermission(); 41 | token = await messaging.getToken(); 42 | } 43 | console.log({ token }); 44 | } catch (error) { 45 | console.error("An error occurred while retrieving token. ", error); 46 | } 47 | 48 | return token; 49 | }; 50 | 51 | const button = document.getElementById("token-button"); 52 | const input = document.getElementById("token-input"); 53 | 54 | if (button && input) { 55 | button.addEventListener("click", () => { 56 | getToken().then(token => { 57 | input.value = token; 58 | 59 | if (token) { 60 | const body = JSON.stringify({ token }); 61 | const headers = { 'Content-Type': 'application/json' }; 62 | const request = new Request("/save-token", { method: "POST", body, headers }); 63 | 64 | fetch(request) 65 | .then(response => response.json()) 66 | .then(response => console.log({ response })); 67 | } 68 | }); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /firebase-push-notifications/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "gcm_sender_id": "103953800507" 3 | } 4 | -------------------------------------------------------------------------------- /firebase-push-notifications/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% block title %}Flask & Firebase{% endblock %} 17 | 18 | 19 | 20 |
21 | {% block body %} 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | {% endblock%} 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /firebase-push-notifications/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block head %} 4 | Token's List 5 | {% endblock %} 6 | 7 | {% block body %} 8 | {% for token in token_list %} 9 | {{token.token}} 10 | {% endfor %} 11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /firebase-push-notifications/views.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template 2 | from models import get_list 3 | 4 | views = Blueprint('views', __name__) 5 | 6 | 7 | @views.route('/') 8 | def index(): 9 | return render_template('index.html') 10 | 11 | 12 | @views.route('/list') 13 | def list(): 14 | token_list = get_list() 15 | return render_template('list.html', token_list=token_list) 16 | -------------------------------------------------------------------------------- /flask-pythonanywhere/app_01_minimal.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def home(): 7 | return "Hello from Flask" 8 | 9 | app.run() 10 | -------------------------------------------------------------------------------- /flask-pythonanywhere/app_02_start.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def home(): 7 | return render_template_string('

Hi from Flask

') 8 | 9 | app.run() 10 | -------------------------------------------------------------------------------- /flask-pythonanywhere/app_03_templates.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def home(): 7 | return render_template('home01.html') 8 | 9 | app.run(debug=True) 10 | -------------------------------------------------------------------------------- /flask-pythonanywhere/app_04_routes.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def home(): 7 | return jsonify({ 'message': 'hi there' }) 8 | 9 | @app.route('/get', methods=['GET']) 10 | def get_message(): 11 | return jsonify({ 'message': 'hi there form a GET call' }) 12 | 13 | @app.route('/post', methods=['POST']) 14 | def post_message(): 15 | return jsonify({ 'message': 'hi there form a POST call' }) 16 | 17 | @app.route('/put', methods=['PUT']) 18 | def put_message(): 19 | return jsonify({ 'message': 'hi there form a PUT call' }) 20 | 21 | @app.route('/delete', methods=['DELETE']) 22 | def delete_message(): 23 | return jsonify({ 'message': 'hi there form a DELETE call' }) 24 | 25 | @app.route('/test') 26 | def test(): 27 | return jsonify({ 'message': 'I\'m a Teapot!' }), 418 28 | 29 | app.run(debug=True) 30 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/css/print/paper.css: -------------------------------------------------------------------------------- 1 | /* Default Print Stylesheet Template 2 | by Rob Glazebrook of CSSnewbie.com 3 | Last Updated: June 4, 2008 4 | 5 | Feel free (nay, compelled) to edit, append, and 6 | manipulate this file as you see fit. */ 7 | 8 | 9 | @media print { 10 | 11 | /* SECTION 1: Set default width, margin, float, and 12 | background. This prevents elements from extending 13 | beyond the edge of the printed page, and prevents 14 | unnecessary background images from printing */ 15 | html { 16 | background: #fff; 17 | width: auto; 18 | height: auto; 19 | overflow: visible; 20 | } 21 | body { 22 | background: #fff; 23 | font-size: 20pt; 24 | width: auto; 25 | height: auto; 26 | border: 0; 27 | margin: 0 5%; 28 | padding: 0; 29 | overflow: visible; 30 | float: none !important; 31 | } 32 | 33 | /* SECTION 2: Remove any elements not needed in print. 34 | This would include navigation, ads, sidebars, etc. */ 35 | .nestedarrow, 36 | .controls, 37 | .fork-reveal, 38 | .share-reveal, 39 | .state-background, 40 | .reveal .progress, 41 | .reveal .backgrounds, 42 | .reveal .slide-number { 43 | display: none !important; 44 | } 45 | 46 | /* SECTION 3: Set body font face, size, and color. 47 | Consider using a serif font for readability. */ 48 | body, p, td, li, div { 49 | font-size: 20pt!important; 50 | font-family: Georgia, "Times New Roman", Times, serif !important; 51 | color: #000; 52 | } 53 | 54 | /* SECTION 4: Set heading font face, sizes, and color. 55 | Differentiate your headings from your body text. 56 | Perhaps use a large sans-serif for distinction. */ 57 | h1,h2,h3,h4,h5,h6 { 58 | color: #000!important; 59 | height: auto; 60 | line-height: normal; 61 | font-family: Georgia, "Times New Roman", Times, serif !important; 62 | text-shadow: 0 0 0 #000 !important; 63 | text-align: left; 64 | letter-spacing: normal; 65 | } 66 | /* Need to reduce the size of the fonts for printing */ 67 | h1 { font-size: 28pt !important; } 68 | h2 { font-size: 24pt !important; } 69 | h3 { font-size: 22pt !important; } 70 | h4 { font-size: 22pt !important; font-variant: small-caps; } 71 | h5 { font-size: 21pt !important; } 72 | h6 { font-size: 20pt !important; font-style: italic; } 73 | 74 | /* SECTION 5: Make hyperlinks more usable. 75 | Ensure links are underlined, and consider appending 76 | the URL to the end of the link for usability. */ 77 | a:link, 78 | a:visited { 79 | color: #000 !important; 80 | font-weight: bold; 81 | text-decoration: underline; 82 | } 83 | /* 84 | .reveal a:link:after, 85 | .reveal a:visited:after { 86 | content: " (" attr(href) ") "; 87 | color: #222 !important; 88 | font-size: 90%; 89 | } 90 | */ 91 | 92 | 93 | /* SECTION 6: more reveal.js specific additions by @skypanther */ 94 | ul, ol, div, p { 95 | visibility: visible; 96 | position: static; 97 | width: auto; 98 | height: auto; 99 | display: block; 100 | overflow: visible; 101 | margin: 0; 102 | text-align: left !important; 103 | } 104 | .reveal pre, 105 | .reveal table { 106 | margin-left: 0; 107 | margin-right: 0; 108 | } 109 | .reveal pre code { 110 | padding: 20px; 111 | border: 1px solid #ddd; 112 | } 113 | .reveal blockquote { 114 | margin: 20px 0; 115 | } 116 | .reveal .slides { 117 | position: static !important; 118 | width: auto !important; 119 | height: auto !important; 120 | 121 | left: 0 !important; 122 | top: 0 !important; 123 | margin-left: 0 !important; 124 | margin-top: 0 !important; 125 | padding: 0 !important; 126 | zoom: 1 !important; 127 | 128 | overflow: visible !important; 129 | display: block !important; 130 | 131 | text-align: left !important; 132 | -webkit-perspective: none; 133 | -moz-perspective: none; 134 | -ms-perspective: none; 135 | perspective: none; 136 | 137 | -webkit-perspective-origin: 50% 50%; 138 | -moz-perspective-origin: 50% 50%; 139 | -ms-perspective-origin: 50% 50%; 140 | perspective-origin: 50% 50%; 141 | } 142 | .reveal .slides section { 143 | visibility: visible !important; 144 | position: static !important; 145 | width: auto !important; 146 | height: auto !important; 147 | display: block !important; 148 | overflow: visible !important; 149 | 150 | left: 0 !important; 151 | top: 0 !important; 152 | margin-left: 0 !important; 153 | margin-top: 0 !important; 154 | padding: 60px 20px !important; 155 | z-index: auto !important; 156 | 157 | opacity: 1 !important; 158 | 159 | page-break-after: always !important; 160 | 161 | -webkit-transform-style: flat !important; 162 | -moz-transform-style: flat !important; 163 | -ms-transform-style: flat !important; 164 | transform-style: flat !important; 165 | 166 | -webkit-transform: none !important; 167 | -moz-transform: none !important; 168 | -ms-transform: none !important; 169 | transform: none !important; 170 | 171 | -webkit-transition: none !important; 172 | -moz-transition: none !important; 173 | -ms-transition: none !important; 174 | transition: none !important; 175 | } 176 | .reveal .slides section.stack { 177 | padding: 0 !important; 178 | } 179 | .reveal section:last-of-type { 180 | page-break-after: avoid !important; 181 | } 182 | .reveal section .fragment { 183 | opacity: 1 !important; 184 | visibility: visible !important; 185 | 186 | -webkit-transform: none !important; 187 | -moz-transform: none !important; 188 | -ms-transform: none !important; 189 | transform: none !important; 190 | } 191 | .reveal section img { 192 | display: block; 193 | margin: 15px 0px; 194 | background: rgba(255,255,255,1); 195 | border: 1px solid #666; 196 | box-shadow: none; 197 | } 198 | 199 | .reveal section small { 200 | font-size: 0.8em; 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/css/theme/dark.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flask-pythonanywhere/notes-export/css/theme/dark.css -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flask-pythonanywhere/notes-export/favicon.ico -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RevealJS : /Users/ricardomartinez/repos/pyDojos/flask-pythonanywhere/notes.md 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 26 | 27 | 32 | 33 | 34 | 35 | 36 |
37 |
48 |
49 | 50 | 51 | 52 | 53 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/lib/css/darcula.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Darcula color scheme from the JetBrains family of IDEs 4 | 5 | */ 6 | 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #2b2b2b; 13 | } 14 | 15 | .hljs { 16 | color: #bababa; 17 | } 18 | 19 | .hljs-strong, 20 | .hljs-emphasis { 21 | color: #a8a8a2; 22 | } 23 | 24 | .hljs-bullet, 25 | .hljs-quote, 26 | .hljs-link, 27 | .hljs-number, 28 | .hljs-regexp, 29 | .hljs-literal { 30 | color: #6896ba; 31 | } 32 | 33 | .hljs-code, 34 | .hljs-selector-class { 35 | color: #a6e22e; 36 | } 37 | 38 | .hljs-emphasis { 39 | font-style: italic; 40 | } 41 | 42 | .hljs-keyword, 43 | .hljs-selector-tag, 44 | .hljs-section, 45 | .hljs-attribute, 46 | .hljs-name, 47 | .hljs-variable { 48 | color: #cb7832; 49 | } 50 | 51 | .hljs-params { 52 | color: #b9b9b9; 53 | } 54 | 55 | .hljs-string { 56 | color: #6a8759; 57 | } 58 | 59 | .hljs-subst, 60 | .hljs-type, 61 | .hljs-built_in, 62 | .hljs-builtin-name, 63 | .hljs-symbol, 64 | .hljs-selector-id, 65 | .hljs-selector-attr, 66 | .hljs-selector-pseudo, 67 | .hljs-template-tag, 68 | .hljs-template-variable, 69 | .hljs-addition { 70 | color: #e0c46c; 71 | } 72 | 73 | .hljs-comment, 74 | .hljs-deletion, 75 | .hljs-meta { 76 | color: #7f7f7f; 77 | } 78 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/lib/css/darkula.css: -------------------------------------------------------------------------------- 1 | /* 2 | Deprecated due to a typo in the name and left here for compatibility purpose only. 3 | Please use darcula.css instead. 4 | */ 5 | 6 | @import url('darcula.css'); 7 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/plugin/math/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for MathJax. 4 | * 5 | * @author Hakim El Hattab 6 | */ 7 | var RevealMath = window.RevealMath || (function(){ 8 | 9 | var options = Reveal.getConfig().math || {}; 10 | options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js'; 11 | options.config = options.config || 'TeX-AMS_HTML-full'; 12 | 13 | loadScript( options.mathjax + '?config=' + options.config, function() { 14 | 15 | MathJax.Hub.Config({ 16 | messageStyle: 'none', 17 | tex2jax: { 18 | inlineMath: [['$','$'],['\\(','\\)']] , 19 | skipTags: ['script','noscript','style','textarea','pre'] 20 | }, 21 | skipStartupTypeset: true 22 | }); 23 | 24 | // Typeset followed by an immediate reveal.js layout since 25 | // the typesetting process could affect slide height 26 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] ); 27 | MathJax.Hub.Queue( Reveal.layout ); 28 | 29 | // Reprocess equations in slides when they turn visible 30 | Reveal.addEventListener( 'slidechanged', function( event ) { 31 | 32 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); 33 | 34 | } ); 35 | 36 | } ); 37 | 38 | function loadScript( url, callback ) { 39 | 40 | var head = document.querySelector( 'head' ); 41 | var script = document.createElement( 'script' ); 42 | script.type = 'text/javascript'; 43 | script.src = url; 44 | 45 | // Wrapper for callback to make sure it only fires once 46 | var finish = function() { 47 | if( typeof callback === 'function' ) { 48 | callback.call(); 49 | callback = null; 50 | } 51 | } 52 | 53 | script.onload = finish; 54 | 55 | // IE 56 | script.onreadystatechange = function() { 57 | if ( this.readyState === 'loaded' ) { 58 | finish(); 59 | } 60 | } 61 | 62 | // Normal browsers 63 | head.appendChild( script ); 64 | 65 | } 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes-export/plugin/notes/notes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles opening of and synchronization with the reveal.js 3 | * notes window. 4 | * 5 | * Handshake process: 6 | * 1. This window posts 'connect' to notes window 7 | * - Includes URL of presentation to show 8 | * 2. Notes window responds with 'connected' when it is available 9 | * 3. This window proceeds to send the current presentation state 10 | * to the notes window 11 | */ 12 | var RevealNotes = (function() { 13 | 14 | function openNotes( notesFilePath ) { 15 | 16 | if( !notesFilePath ) { 17 | var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path 18 | jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path 19 | notesFilePath = jsFileLocation + 'notes.html'; 20 | } 21 | 22 | var notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' ); 23 | 24 | // Allow popup window access to Reveal API 25 | notesPopup.Reveal = this.Reveal; 26 | 27 | /** 28 | * Connect to the notes window through a postmessage handshake. 29 | * Using postmessage enables us to work in situations where the 30 | * origins differ, such as a presentation being opened from the 31 | * file system. 32 | */ 33 | function connect() { 34 | // Keep trying to connect until we get a 'connected' message back 35 | var connectInterval = setInterval( function() { 36 | notesPopup.postMessage( JSON.stringify( { 37 | namespace: 'reveal-notes', 38 | type: 'connect', 39 | url: window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search, 40 | state: Reveal.getState() 41 | } ), '*' ); 42 | }, 500 ); 43 | 44 | window.addEventListener( 'message', function( event ) { 45 | var data = JSON.parse( event.data ); 46 | if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { 47 | clearInterval( connectInterval ); 48 | onConnected(); 49 | } 50 | } ); 51 | } 52 | 53 | /** 54 | * Posts the current slide data to the notes window 55 | */ 56 | function post( event ) { 57 | 58 | var slideElement = Reveal.getCurrentSlide(), 59 | notesElement = slideElement.querySelector( 'aside.notes' ), 60 | fragmentElement = slideElement.querySelector( '.current-fragment' ); 61 | 62 | var messageData = { 63 | namespace: 'reveal-notes', 64 | type: 'state', 65 | notes: '', 66 | markdown: false, 67 | whitespace: 'normal', 68 | state: Reveal.getState() 69 | }; 70 | 71 | // Look for notes defined in a slide attribute 72 | if( slideElement.hasAttribute( 'data-notes' ) ) { 73 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 74 | messageData.whitespace = 'pre-wrap'; 75 | } 76 | 77 | // Look for notes defined in a fragment 78 | if( fragmentElement ) { 79 | var fragmentNotes = fragmentElement.querySelector( 'aside.notes' ); 80 | if( fragmentNotes ) { 81 | notesElement = fragmentNotes; 82 | } 83 | else if( fragmentElement.hasAttribute( 'data-notes' ) ) { 84 | messageData.notes = fragmentElement.getAttribute( 'data-notes' ); 85 | messageData.whitespace = 'pre-wrap'; 86 | 87 | // In case there are slide notes 88 | notesElement = null; 89 | } 90 | } 91 | 92 | // Look for notes defined in an aside element 93 | if( notesElement ) { 94 | messageData.notes = notesElement.innerHTML; 95 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 96 | } 97 | 98 | notesPopup.postMessage( JSON.stringify( messageData ), '*' ); 99 | 100 | } 101 | 102 | /** 103 | * Called once we have established a connection to the notes 104 | * window. 105 | */ 106 | function onConnected() { 107 | 108 | // Monitor events that trigger a change in state 109 | Reveal.addEventListener( 'slidechanged', post ); 110 | Reveal.addEventListener( 'fragmentshown', post ); 111 | Reveal.addEventListener( 'fragmenthidden', post ); 112 | Reveal.addEventListener( 'overviewhidden', post ); 113 | Reveal.addEventListener( 'overviewshown', post ); 114 | Reveal.addEventListener( 'paused', post ); 115 | Reveal.addEventListener( 'resumed', post ); 116 | 117 | // Post the initial state 118 | post(); 119 | 120 | } 121 | 122 | connect(); 123 | 124 | } 125 | 126 | if( !/receiver/i.test( window.location.search ) ) { 127 | 128 | // If the there's a 'notes' query set, open directly 129 | if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) { 130 | openNotes(); 131 | } 132 | 133 | // Open the notes when the 's' key is hit 134 | document.addEventListener( 'keydown', function( event ) { 135 | // Disregard the event if the target is editable or a 136 | // modifier is present 137 | if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return; 138 | 139 | // Disregard the event if keyboard is disabled 140 | if ( Reveal.getConfig().keyboard === false ) return; 141 | 142 | if( event.keyCode === 83 ) { 143 | event.preventDefault(); 144 | openNotes(); 145 | } 146 | }, false ); 147 | 148 | // Show our keyboard shortcut in the reveal.js help overlay 149 | if( window.Reveal ) Reveal.registerKeyboardShortcut( 'S', 'Speaker notes view' ); 150 | 151 | } 152 | 153 | return { open: openNotes }; 154 | 155 | })(); 156 | -------------------------------------------------------------------------------- /flask-pythonanywhere/notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme: "black" 3 | transition: "zoom" 4 | highlightTheme: "darkula" 5 | --- 6 | 7 | ## June Dojo 8 | # Flask & pythonanywhere 9 | #### Ricardo Martinez. 10 | 11 | --- 12 | 13 | # Flask 14 | minimalistic (or micro) and very customizable web framework based on Werkzeug. 15 | 16 | --- 17 | 18 | # pythonanywhere 19 | Is a cloud platform for hosting & running python apps. 20 | 21 | --- 22 | 23 | # Goals 24 | 25 | 1. Inspect Flask basic structure 26 | 2. What is a REST API? 27 | 3. Design a translation API 28 | 4. Publish into Pythonanywhere 29 | 30 | --- 31 | 32 | ### 1. Inspect Flask basic structure 33 | 34 | 35 | -- 36 | 37 | ### Flask basic structure 38 | 39 | ``` 40 | from flask import Flask 41 | 42 | app = Flask(__name__) 43 | 44 | @app.route('/') 45 | def home(): 46 | return 'Hi there from Flask' 47 | 48 | app.run() 49 | ``` 50 | 51 | --- 52 | 53 | ### 2. What is a REST API? 54 | 55 | -- 56 | 57 | ### REST API 58 | 59 | As the same way than an traditional API exposes a layer for interact with data structures or services, the REST Api applies the same approach for web resources. 60 | 61 | -- 62 | 63 | ### most used HTTP Verbs 64 | 65 | - GET 66 | - POST 67 | - PUT (sometimes UPDATE) 68 | - DELETE 69 | 70 | -- 71 | 72 | ### most used HTTP Responses 73 | 74 | - 200 -> Success 75 | - 201 -> Created 76 | - 400 -> Bad request 77 | - 401 -> Unauthorized 78 | - 403 -> Forbidden 79 | - 500 -> Server error 80 | - 418 -> ... 81 | 82 | -- 83 | 84 | ### Http Error 418 - I'm a teapot 85 | The HTTP 418 I'm a teapot client error response code indicates that the server refuses to brew coffee because it is a teapot. This error is a reference of Hyper Text Coffee Pot Control Protocol which was an April Fools' joke in 1998. 86 | 87 | --- 88 | 89 | ### Design a translation API 90 | 91 | Based on the HTTP verbs, we can use the following structure for describing our resources: 92 | 93 | - 94 | -------------------------------------------------------------------------------- /flask-pythonanywhere/templates/home01.html: -------------------------------------------------------------------------------- 1 |

Hi from Flask

2 |

This is a rendered template

3 | -------------------------------------------------------------------------------- /flask-pythonanywhere/translate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flask-pythonanywhere/translate/__init__.py -------------------------------------------------------------------------------- /flask-pythonanywhere/translate/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import socket 4 | from flask import Flask, jsonify, abort, request 5 | 6 | path = os.getcwd() + '/../' 7 | print(path) 8 | if path not in sys.path: 9 | sys.path.append(path) 10 | 11 | from translate.db import translateDB 12 | 13 | app = Flask(__name__) 14 | 15 | @app.route('/words') 16 | def get_all_words(): 17 | result = translateDB.get_all_translations() 18 | 19 | return jsonify({ 'words': result }), 200 20 | 21 | @app.route('/words/') 22 | def get_word(word): 23 | result = translateDB.get_translation(word) 24 | 25 | if result is not None: 26 | return jsonify({ 'trans': result }), 200 27 | 28 | return jsonify({ 'message': None }), 404 29 | 30 | @app.route('/words', methods=['POST']) 31 | def post_word(): 32 | request_data = request.get_json() 33 | word = request_data.get('word') 34 | trans = request_data.get('trans') 35 | 36 | if word and trans: 37 | translateDB.add_translation(word, trans) 38 | return jsonify({ word: trans }), 200 39 | 40 | return jsonify({ 'message': 'error' }), 400 41 | 42 | @app.route('/words/', methods=['PUT']) 43 | def put_word(word): 44 | request_data = request.get_json() 45 | trans = request_data.get('trans') 46 | 47 | if word and trans: 48 | if translateDB.get_translation(word) is not None: 49 | translateDB.update_translation(word, trans) 50 | return jsonify({ word: trans }), 200 51 | 52 | return jsonify({ 'message': 'error' }), 404 53 | 54 | return jsonify({ 'message': 'error' }), 400 55 | 56 | @app.route('/words/', methods=['DELETE']) 57 | def delete_word(word): 58 | if word: 59 | if translateDB.get_translation(word) is not None: 60 | translateDB.remove_translation(word) 61 | return jsonify({ 'message': 'deleted' }), 200 62 | 63 | return jsonify({ 'message': 'error' }), 404 64 | 65 | return jsonify({ 'message': 'error' }), 400 66 | 67 | if 'live' not in socket.gethostname(): 68 | app.run(debug=True) 69 | -------------------------------------------------------------------------------- /flask-pythonanywhere/translate/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class TranslateDB(object): 4 | __db_name = 'translate.db' 5 | __table_name = 'translations' 6 | 7 | def __init__(self): 8 | sql = 'CREATE TABLE IF NOT EXISTS {table} (word string, trans string)' \ 9 | .format(table=self.__table_name) 10 | 11 | self.__execute(sql) 12 | 13 | def __connect_db(self): 14 | self.__connection = sqlite3.connect(self.__db_name) 15 | self.__cursor = self.__connection.cursor() 16 | 17 | return self 18 | 19 | def __close_db(self): 20 | self.__connection.commit() 21 | self.__connection.close() 22 | 23 | def __execute(self, sql): 24 | self.__connect_db() \ 25 | .__cursor.execute(sql) 26 | 27 | def get_all_translations(self): 28 | sql = 'SELECT word, trans FROM {table}' \ 29 | .format(table=self.__table_name) 30 | self.__execute(sql) 31 | result = [{ row[0]: row[1] } for row in self.__cursor.fetchall()] 32 | self.__close_db() 33 | 34 | return result 35 | 36 | def add_translation(self, word, trans): 37 | sql = 'INSERT INTO {table} (word, trans) VALUES ("{word}", "{trans}")' \ 38 | .format(table=self.__table_name, word=word, trans=trans) 39 | self.__execute(sql) 40 | self.__close_db() 41 | 42 | def get_translation(self, word): 43 | sql = 'SELECT trans FROM {table} WHERE word="{word}"' \ 44 | .format(table=self.__table_name, word=word) 45 | self.__execute(sql) 46 | result = self.__cursor.fetchone() 47 | self.__close_db() 48 | 49 | if result: 50 | return result[0] 51 | 52 | return None 53 | 54 | def update_translation(self, word, trans): 55 | sql = 'UPDATE {table} SET trans="{trans}" WHERE word="{word}"' \ 56 | .format(table=self.__table_name, word=word, trans=trans) 57 | self.__execute(sql) 58 | self.__close_db() 59 | 60 | def remove_translation(self, word): 61 | sql = 'DELETE FROM {table} WHERE word="{word}"' \ 62 | .format(table=self.__table_name, word=word) 63 | self.__execute(sql) 64 | self.__close_db() 65 | 66 | 67 | translateDB = TranslateDB() 68 | -------------------------------------------------------------------------------- /flask-pythonanywhere/translate/translate.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flask-pythonanywhere/translate/translate.db -------------------------------------------------------------------------------- /flaskgame/README.md: -------------------------------------------------------------------------------- 1 | Playing a bit with Flask 2 | ======================== 3 | 4 | ![Flask Logo](http://flask.pocoo.org/static/logo/flask.svg) 5 | 6 | Flask is a great lightweight web framework ideal to build fast and reliable web 7 | services and much more. 8 | 9 | This dojo is inspired on 10 | [CD Ninja Gold repository](https://github.com/felisadeang/CD_ninjagold). 11 | 12 | Also taken some parts of this 13 | [Flask Dojo](https://github.com/ranisalt/flask-dojo). 14 | 15 | More information about Flask in the official website 16 | [http://flask.pocoo.org/](http://flask.pocoo.org/). 17 | 18 | ## Dojo Steps ## 19 | 20 | 1. Hello world! 21 | 2. Context introduction. 22 | 3. Start Fighting. 23 | ``` 24 | curl http://localhost:5000 25 | curl -XDELETE http://localhost:5000 26 | curl -XPOST http://localhost:5000/fight 27 | ``` 28 | 4. Do Punch. 29 | ``` 30 | curl -XPUT http://localhost:5000/fight 31 | ``` 32 | 5. Get current Score. 33 | ``` 34 | curl http://localhost:5000/fight 35 | ``` 36 | 6. End Fighting. 37 | ``` 38 | curl -XDELETE http://localhost:5000/fight 39 | ``` 40 | 7. URL query string to set a prefix for the Score response. 41 | ``` 42 | curl http://localhost:5000/fight?score_prefix=Damage%20is%3A%20 43 | ``` 44 | 7. Post fields to set a multiplier of doing punches 45 | ``` 46 | curl -XPUT --data "multiplier=2" http://localhost:5000/fight 47 | ``` 48 | 8. Post fields as JSON 49 | ``` 50 | curl -XPUT --header "Content-Type:application/json" --data "{\"multiplier\": 2, \"whining_choices\": [\"AAArg...\", \"Uaala\"]}" http://localhost:5000/fight 51 | ``` 52 | 53 | n. Template, Upload file, Authorization, encoding,... 54 | n. Fighting automation. 55 | 56 | ## HTTP Punch ## 57 | 58 | The approach for this dojo is a game based practice and we are going to learn 59 | some basic features of Flask in an entertaining way. The principles and rules 60 | for this game below: 61 | 62 | * Developer = Player. 63 | * Limit of punches for each fight = 25. 64 | * Minimum Damage starts at 0 and Maximum Damage is at 100. 65 | * Third party Damage is a random result value returned in responses, and is not 66 | cumulative. 67 | * The result for each HTTP request is a random number between Minimum Damage and 68 | Maximum Damage range. 69 | * Each punch updates the Minimum Damage value with a random result. 70 | * HTTP Request = Playing turn. 71 | * HTTP POST Endpoint Request = Start fighting. 201 Created status code. 72 | * HTTP PUT Endpoint Request = Do punch on started fight. 204 No Content status 73 | code. 74 | * HTTP GET Endpoint Request = Returns the current Minimum Range as current 75 | fighting Score. 200 Success status. 76 | * HTTP DELETE Endpoint Request = End fighting, last Score got by GET is the final 77 | Score. 204 No Content status code. 78 | 79 | 80 | ### Penalties ### 81 | 82 | * If Minimum Damage reaches Maximum Damage, the score will turn to 0, and there 83 | is no chance to increase anymore. 84 | * If number of done punches exceeds the punches limit, the score will turn to 0, 85 | and there is no chance to increase anymore. -------------------------------------------------------------------------------- /flaskgame/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flaskgame/__init__.py -------------------------------------------------------------------------------- /flaskgame/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flaskgame/src/__init__.py -------------------------------------------------------------------------------- /flaskgame/src/endpoint.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from flask import Flask, request, abort, render_template 4 | 5 | from flaskgame.src.fightvalue import FightValue 6 | from flaskgame.src.randompunchservice import RandomPunchService 7 | 8 | app = Flask(__name__) 9 | 10 | is_ready = None 11 | fight = None 12 | final_score = 0 13 | 14 | 15 | @app.route('/', methods=['GET', 'DELETE']) 16 | def ready(): 17 | global is_ready, fight, final_score 18 | if request.method not in ['GET', 'DELETE']: 19 | return abort(405) 20 | if request.method == 'DELETE': 21 | fight = None 22 | final_score = 0 23 | _update_score() 24 | is_ready = True 25 | return _render(message='Fight ready to start!', score=final_score) 26 | 27 | 28 | @app.route('/fight', methods=['POST', 'PUT', 'GET', 'DELETE']) 29 | def fighting(): 30 | global fight, final_score 31 | if request.method not in ['POST', 'PUT', 'GET', 'DELETE']: 32 | return abort(405) 33 | 34 | query_args = request.args.to_dict() 35 | 36 | if request.method == 'POST': 37 | return _start() 38 | 39 | if request.method == 'PUT': 40 | multiplier = None 41 | whining_choices = None 42 | if request.is_json: 43 | request_data = request.json 44 | else: 45 | request_data = request.form 46 | if 'multiplier' in request_data: 47 | multiplier = int(request_data['multiplier']) 48 | if 'whining_choices' in request_data: 49 | whining_choices = request_data['whining_choices'] 50 | return _punch(multiplier, whining_choices) 51 | 52 | if request.method == 'GET': 53 | return _score(query_args) 54 | 55 | if request.method == 'DELETE': 56 | return _end() 57 | 58 | 59 | def _update_score(): 60 | global fight, final_score 61 | if fight: 62 | final_score = fight.current_damage 63 | 64 | 65 | def _render(with_template=True, **kwargs): 66 | if with_template: 67 | return render_template('index.html', **kwargs) 68 | else: 69 | if 'message' in kwargs: 70 | return kwargs['message'] 71 | 72 | 73 | def _end(): 74 | global fight, final_score 75 | if fight: 76 | final_score = fight.current_damage 77 | fight = None 78 | return _render(False, message='Fight ended') 79 | 80 | 81 | def _score(query_args): 82 | global fight 83 | score_prefix = '' 84 | if 'score_prefix' in query_args: 85 | score_prefix = query_args['score_prefix'] 86 | _update_score() 87 | score_value = final_score 88 | if fight: 89 | score_value = fight.current_damage 90 | return _render(False, message='{!s}{:d}'.format(score_prefix, score_value)) 91 | 92 | 93 | def _punch(multiplier, whining_choices=None): 94 | global fight 95 | 96 | if not fight: 97 | return abort(400) 98 | 99 | if not whining_choices: 100 | whining_choices = ['Uuuffh', 'Oughhh', 'Aix', 'Pufghfs'] 101 | if not multiplier: 102 | multiplier = 1 103 | 104 | while multiplier: 105 | fight.punch() 106 | multiplier -= 1 107 | 108 | damage = fight.current_damage 109 | whining = random.choice(whining_choices) 110 | return _render(False, message='{!s} ({:d})'.format(whining, damage)) 111 | 112 | 113 | def _start(): 114 | global fight 115 | if fight: 116 | return abort(400) 117 | puncher = RandomPunchService(min_value=0, max_damage=100) 118 | fight = FightValue(punch_service=puncher) 119 | return _render(False, message='Fighting!!') 120 | 121 | 122 | if __name__ == '__main__': 123 | app.run(debug=True) 124 | -------------------------------------------------------------------------------- /flaskgame/src/fightvalue.py: -------------------------------------------------------------------------------- 1 | from flaskgame.src.punchservice import PunchService 2 | 3 | 4 | class FightValue: 5 | 6 | MIN_DAMAGE = 0 7 | MAX_PUNCHES = 25 8 | 9 | def __init__(self, punch_service: PunchService): 10 | self._current_damage = self.MIN_DAMAGE 11 | self._punch_service = punch_service 12 | self._punch_counter = 1 13 | 14 | @property 15 | def current_damage(self): 16 | return self._current_damage 17 | 18 | def punch(self): 19 | if self._punch_counter >= self.MAX_PUNCHES: 20 | self._current_damage = self.MIN_DAMAGE 21 | return 22 | try: 23 | self._current_damage = self._punch_service.punch() 24 | self._punch_counter += 1 25 | except ValueError: 26 | self._current_damage = self.MIN_DAMAGE 27 | 28 | -------------------------------------------------------------------------------- /flaskgame/src/punchservice.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class PunchService(metaclass=ABCMeta): 5 | 6 | @abstractmethod 7 | def punch(self): 8 | pass 9 | -------------------------------------------------------------------------------- /flaskgame/src/randompunchservice.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from flaskgame.src.punchservice import PunchService 4 | 5 | 6 | class RandomPunchService(PunchService): 7 | 8 | def __init__(self, min_value, max_damage): 9 | self._min_value = min_value 10 | self._max_damage = max_damage 11 | 12 | def punch(self): 13 | self._min_value = random.randrange(self._min_value, self._max_damage + 1) 14 | if self._min_value >= self._max_damage: 15 | raise ValueError('Reached maximum punch value') 16 | return self._min_value 17 | -------------------------------------------------------------------------------- /flaskgame/src/static/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | #wrapper { 6 | width: 1070px; 7 | margin: 0px auto; 8 | padding: 5px; 9 | overflow: hidden; 10 | font-family: Helvetica, Verdana; 11 | font-size: 11pt; 12 | color: black; 13 | } 14 | #fouroptions { 15 | padding: 35px; 16 | } 17 | .fchc { 18 | margin: 15px; 19 | padding: 15px; 20 | border: 1px solid black; 21 | border-radius: 5px; 22 | text-align: center; 23 | display: inline-block; 24 | } 25 | #activities{ 26 | margin-left: 25px; 27 | } 28 | #activities_container{ 29 | margin: 5px 5px 5px 35px; 30 | padding: 5px; 31 | width: 800px; 32 | height: 300px; 33 | border: 1px dotted black; 34 | border-radius: 3px; 35 | } 36 | -------------------------------------------------------------------------------- /flaskgame/src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTTP Punch 6 | 7 | 8 |

{{ message }}

9 |

Score: {{ score }}

10 |
11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /flaskgame/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/flaskgame/tests/__init__.py -------------------------------------------------------------------------------- /flaskgame/tests/fighttest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from flaskgame.src.fightvalue import FightValue 4 | from flaskgame.tests.mockpunchservice import MockPunchService 5 | 6 | 7 | class TestFight(unittest.TestCase): 8 | 9 | def setUp(self): 10 | pass 11 | 12 | def test_punches_limit(self): 13 | puncher = MockPunchService(1) 14 | fight = FightValue(puncher) 15 | counter = 1 16 | while counter < FightValue.MAX_PUNCHES: 17 | fight.punch() 18 | counter += 1 19 | assert counter == FightValue.MAX_PUNCHES 20 | fight.punch() 21 | assert fight.current_damage == FightValue.MIN_DAMAGE 22 | 23 | def test_damage_limit(self): 24 | INC_VALUE = 20 25 | MAX_DAMAGE = 100 26 | puncher = MockPunchService(INC_VALUE, MAX_DAMAGE) 27 | fight = FightValue(puncher) 28 | counter = 1 29 | while counter <= 5: 30 | fight.punch() 31 | counter += 1 32 | assert fight.current_damage == MAX_DAMAGE 33 | fight.punch() 34 | assert fight.current_damage == FightValue.MIN_DAMAGE 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /flaskgame/tests/hello.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | 5 | @app.route('/') 6 | def hello_world(): 7 | return 'Hello, World!' 8 | -------------------------------------------------------------------------------- /flaskgame/tests/mockpunchservice.py: -------------------------------------------------------------------------------- 1 | from flaskgame.src.punchservice import PunchService 2 | 3 | 4 | class MockPunchService(PunchService): 5 | 6 | def __init__(self, inc_value, max_damage=1000): 7 | self._value = 0 8 | self._inc_value = inc_value 9 | self._max_damage = max_damage 10 | 11 | def punch(self): 12 | self._value += self._inc_value 13 | if self._value > self._max_damage: 14 | raise ValueError('Reached maximum punch value') 15 | return self._value 16 | -------------------------------------------------------------------------------- /functional_python/coordinates_kata.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import itertools 3 | from haversine import haversine 4 | 5 | 6 | def is_europe(row): 7 | continent = row[5] 8 | return continent == 'Europe' 9 | 10 | 11 | def pick_lat_lon(city_tuple): 12 | lat = float(city_tuple[2]) 13 | lon = float(city_tuple[3]) 14 | return lat, lon 15 | 16 | 17 | def get_distance(two_cities_tuple): 18 | coordinates = map(pick_lat_lon, two_cities_tuple) 19 | return haversine(*coordinates) 20 | 21 | 22 | with open('country-capitals.csv') as csv_file: 23 | capitals_iterator = csv.reader(csv_file) 24 | european_capitals = filter(is_europe, capitals_iterator) 25 | city_pairs = itertools.combinations(european_capitals, 2) 26 | remotest_cities = max(city_pairs, key=get_distance) 27 | 28 | print(get_distance(remotest_cities)) 29 | -------------------------------------------------------------------------------- /functional_python/recursion_limit.py: -------------------------------------------------------------------------------- 1 | from sys import getrecursionlimit, setrecursionlimit 2 | 3 | 4 | def recursion_sum(n): 5 | if n <= 1: 6 | return n 7 | else: 8 | return n + recursion_sum(n-1) 9 | 10 | 11 | print(recursion_sum(1000)) 12 | print(getrecursionlimit()) 13 | setrecursionlimit(1002) 14 | print(recursion_sum(1000)) 15 | -------------------------------------------------------------------------------- /functional_python/robot_fun.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from functools import reduce 3 | from itertools import cycle 4 | from operator import add 5 | 6 | 7 | Robot = namedtuple('Robot', ('x', 'y', 'direction')) 8 | direction = cycle(('NORTH', 'EAST', 'SOUTH', 'WEST')) 9 | 10 | 11 | def turn_right(robot): 12 | if next(direction) != robot.direction: 13 | return turn_right(robot) 14 | return Robot(robot.x, robot.y, next(direction)) 15 | 16 | 17 | def turn_left(robot): 18 | return turn_right(turn_right(turn_right(robot))) 19 | 20 | 21 | def advance(robot): 22 | advance_map = { 23 | 'NORTH': (0, 1), 24 | 'EAST': (1, 0), 25 | 'SOUTH': (0, -1), 26 | 'WEST': (-1, 0) 27 | } 28 | new_coordinates = map(add, (robot.x, robot.y), advance_map[robot.direction]) 29 | return Robot(*new_coordinates, robot.direction) 30 | 31 | 32 | def move(robot, command): 33 | movement_map = { 34 | 'A': advance, 35 | 'L': turn_left, 36 | 'R': turn_right 37 | } 38 | return movement_map[command](robot) 39 | 40 | 41 | def execute_commands(robot, commands): 42 | if not commands: 43 | return robot 44 | head_command, *tail_commands = commands 45 | return execute_commands(move(robot, head_command), tail_commands) 46 | 47 | 48 | def print_position(robot): 49 | print(robot.x, robot.y, robot.direction) 50 | 51 | 52 | if __name__ == '__main__': 53 | robot_position = input('Robot position:\n').split() 54 | robot_position = *map(int, robot_position[:2]), robot_position[2] 55 | commands = input('Commands:\n') 56 | print_position(execute_commands(Robot(*robot_position), commands)) 57 | -------------------------------------------------------------------------------- /functional_python/robot_oop.py: -------------------------------------------------------------------------------- 1 | directions = ['North', 'East', 'South', 'West'] 2 | 3 | 4 | class Robot: 5 | def __init__(self, x, y, direction): 6 | self.x = x 7 | self.y = y 8 | self.direction = direction 9 | 10 | @property 11 | def position(self): 12 | return self.x, self.y, self.direction 13 | 14 | def turn_right(self): 15 | self.direction = directions[(directions.index(self.direction) + 1) % 4] 16 | 17 | def turn_left(self): 18 | self.direction = directions[(directions.index(self.direction) + 3) % 4] 19 | 20 | def advance(self): 21 | direction = self.position[2] 22 | if direction == 'North': 23 | self.y += 1 24 | if direction == 'East': 25 | self.x += 1 26 | if direction == 'South': 27 | self.y -= 1 28 | if direction == 'West': 29 | self.x -= 1 30 | 31 | def move(self, letter): 32 | if letter == 'R': 33 | self.turn_right() 34 | if letter == 'L': 35 | self.turn_left() 36 | if letter == 'A': 37 | self.advance() 38 | 39 | def execute_movements(self, s): 40 | for letter in s: 41 | self.move(letter) 42 | 43 | 44 | if __name__=='__main__': 45 | input1 = input('Robot starting position:\n').split() 46 | x = int(input1[0]) 47 | y = int(input1[1]) 48 | direction = input1[2] 49 | robot = Robot(x, y, direction) 50 | input2 = input('Movements:\n') 51 | robot.execute_movements(input2) 52 | print(robot.position) 53 | -------------------------------------------------------------------------------- /functional_python/test_robot_oop.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from robot_oop import Robot 3 | 4 | 5 | class RobotTests(unittest.TestCase): 6 | 7 | def test_position(self): 8 | robot = Robot(1, 1, 'North') 9 | self.assertEqual(robot.position, (1, 1, 'North')) 10 | 11 | def test_turn_right(self): 12 | robot = Robot(1, 1, 'North') 13 | robot.move('R') 14 | self.assertEqual(robot.position, (1, 1, 'East')) 15 | robot.move('R') 16 | self.assertEqual(robot.position, (1, 1, 'South')) 17 | robot.move('R') 18 | self.assertEqual(robot.position, (1, 1, 'West')) 19 | robot.move('R') 20 | self.assertEqual(robot.position, (1, 1, 'North')) 21 | 22 | def test_turn_left(self): 23 | robot = Robot(1, 1, 'North') 24 | robot.move('L') 25 | self.assertEqual(robot.position, (1, 1, 'West')) 26 | robot.move('L') 27 | self.assertEqual(robot.position, (1, 1, 'South')) 28 | robot.move('L') 29 | self.assertEqual(robot.position, (1, 1, 'East')) 30 | robot.move('L') 31 | self.assertEqual(robot.position, (1, 1, 'North')) 32 | 33 | def test_advance(self): 34 | robot = Robot(1, 1, 'North') 35 | robot.move('A') 36 | self.assertEqual(robot.position, (1, 2, 'North')) 37 | 38 | def test_movements(self): 39 | robot = Robot(7, 3, 'North') 40 | robot.execute_movements('RAALAL') 41 | self.assertEqual(robot.position, (9, 4, 'West')) 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /gilded-rose/dojo_cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/gilded-rose/dojo_cycle.png -------------------------------------------------------------------------------- /gilded-rose/dojos.css: -------------------------------------------------------------------------------- 1 | section p { 2 | text-align: left; 3 | } 4 | 5 | .figure p { 6 | text-align: center; 7 | } 8 | 9 | section pre { 10 | background-color: white; 11 | color: black; 12 | } 13 | -------------------------------------------------------------------------------- /gilded-rose/generate_fixture.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from gilded_rose import GildedRose 4 | from item import Item 5 | 6 | 7 | def generate_fixture(days=None): 8 | items = [ 9 | Item( 10 | name="+5 Dexterity Vest", 11 | sell_in=10, 12 | quality=20, 13 | ), 14 | Item( 15 | name="Aged Brie", 16 | sell_in=2, 17 | quality=0, 18 | ), 19 | Item( 20 | name="Elixir of the Mongoose", 21 | sell_in=5, 22 | quality=7 23 | ), 24 | Item( 25 | name="Sulfuras, Hand of Ragnaros", 26 | sell_in=0, 27 | quality=80, 28 | ), 29 | Item( 30 | name="Sulfuras, Hand of Ragnaros", 31 | sell_in=-1, 32 | quality=80, 33 | ), 34 | Item( 35 | name="Backstage passes to a TAFKAL80ETC concert", 36 | sell_in=15, 37 | quality=20, 38 | ), 39 | Item( 40 | name="Backstage passes to a TAFKAL80ETC concert", 41 | sell_in=10, 42 | quality=49, 43 | ), 44 | Item( 45 | name="Backstage passes to a TAFKAL80ETC concert", 46 | sell_in=5, 47 | quality=49, 48 | ), 49 | Item( 50 | name="Conjured Mana Cake", 51 | sell_in=3, 52 | quality=6, 53 | ), 54 | ] 55 | 56 | if days is None: 57 | days = 2 58 | for day in range(days): 59 | print("-------- day %s --------" % day) 60 | print("name, sellIn, quality") 61 | for item in items: 62 | print(item) 63 | print("") 64 | GildedRose(items).update_quality() 65 | 66 | if __name__ == "__main__": 67 | days = 2 68 | import sys 69 | if len(sys.argv) > 1: 70 | days = int(sys.argv[1]) + 1 71 | generate_fixture(days) 72 | -------------------------------------------------------------------------------- /gilded-rose/generate_fixtures: -------------------------------------------------------------------------------- 1 | python generate_fixture.py 27 > golden_master.txt -------------------------------------------------------------------------------- /gilded-rose/gilded_rose.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | aged_brie = "Aged Brie" 4 | backstage = "Backstage passes to a TAFKAL80ETC concert" 5 | sulfuras = "Sulfuras, Hand of Ragnaros" 6 | 7 | 8 | class GildedRose(object): 9 | 10 | def __init__(self, items): 11 | self.items = items 12 | 13 | def update_quality(self): 14 | for item in self.items: 15 | if item.name != aged_brie and item.name != backstage: 16 | if item.quality > 0: 17 | if item.name != sulfuras: 18 | item.quality = item.quality - 1 19 | else: 20 | if item.quality < 50: 21 | item.quality = item.quality + 1 22 | if item.name == backstage: 23 | if item.sell_in < 11: 24 | if item.quality < 50: 25 | item.quality = item.quality + 1 26 | if item.sell_in < 6: 27 | if item.quality < 50: 28 | item.quality = item.quality + 1 29 | if item.name != sulfuras: 30 | item.sell_in = item.sell_in - 1 31 | if item.sell_in < 0: 32 | if item.name != aged_brie: 33 | if item.name != backstage: 34 | if item.quality > 0: 35 | if item.name != sulfuras: 36 | item.quality = item.quality - 1 37 | else: 38 | item.quality = item.quality - item.quality 39 | else: 40 | if item.quality < 50: 41 | item.quality = item.quality + 1 42 | -------------------------------------------------------------------------------- /gilded-rose/item.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Item: 5 | def __init__(self, name, sell_in, quality): 6 | self.name = name 7 | self.sell_in = sell_in 8 | self.quality = quality 9 | 10 | def __repr__(self): 11 | return "%s, %s, %s" % (self.name, self.sell_in, self.quality) 12 | -------------------------------------------------------------------------------- /gilded-rose/run_tests: -------------------------------------------------------------------------------- 1 | pytest . && python generate_fixture.py 27 > test_output.txt && diff test_output.txt golden_master.txt -------------------------------------------------------------------------------- /gilded-rose/test_gilded_rose.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from gilded_rose import GildedRose 4 | from item import Item 5 | 6 | 7 | def test_foo(): 8 | items = [Item("foo", 0, 0)] 9 | gilded_rose = GildedRose(items) 10 | gilded_rose.update_quality() 11 | assert "fixme" == items[0].name 12 | -------------------------------------------------------------------------------- /graphql/ariadne-api-example/api.py: -------------------------------------------------------------------------------- 1 | from ariadne import load_schema_from_path, QueryType, make_executable_schema 2 | from ariadne.asgi import GraphQL 3 | import logging 4 | import requests 5 | 6 | 7 | # load schema from file... 8 | schema = load_schema_from_path("schema.graphql") 9 | 10 | 11 | # Create type instance for Query type defined in our schema... 12 | query = QueryType() 13 | 14 | 15 | @query.field("Planet") 16 | def resolve_planet(*_, name): 17 | logging.info("Making API request to swapi.co") 18 | response = requests.get("https://swapi.co/api/planets").json() 19 | for planet in response["results"]: 20 | if planet["name"] == name: 21 | residents = [] 22 | for api_url in planet["residents"]: 23 | logging.info("Making API request to swapi.co") 24 | response = requests.get(api_url).json() 25 | residents.append({"name": response["name"], "height": response["height"]}) 26 | return {"name": name, "residents": residents} 27 | 28 | 29 | executable_schema = make_executable_schema(schema, query) 30 | app = GraphQL(executable_schema, debug=True) 31 | -------------------------------------------------------------------------------- /graphql/ariadne-api-example/requirements.txt: -------------------------------------------------------------------------------- 1 | ariadne==0.5.0 2 | requests==2.22.0 3 | uvicorn==0.8.4 4 | -------------------------------------------------------------------------------- /graphql/ariadne-api-example/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | Planet(name: String!): Planet 3 | } 4 | 5 | type Planet { 6 | name: String! 7 | residents: [Person!] 8 | } 9 | 10 | type Person { 11 | name: String! 12 | height: Int 13 | } 14 | -------------------------------------------------------------------------------- /graphql/ariadne-api/api.py: -------------------------------------------------------------------------------- 1 | from ariadne import load_schema_from_path, QueryType, make_executable_schema 2 | from ariadne.asgi import GraphQL 3 | 4 | 5 | # load schema from file... 6 | schema = load_schema_from_path("schema.graphql") 7 | 8 | 9 | # Create type instance for Query type defined in our schema... 10 | query = QueryType() 11 | 12 | 13 | @query.field("Film") 14 | def resolve_film(*_, title): 15 | #... 16 | pass 17 | 18 | 19 | # executable_schema = make_executable_schema(schema, query) 20 | # app = GraphQL(executable_schema, debug=True) 21 | -------------------------------------------------------------------------------- /graphql/ariadne-api/requirements.txt: -------------------------------------------------------------------------------- 1 | ariadne==0.5.0 2 | uvicorn==0.8.4 3 | -------------------------------------------------------------------------------- /graphql/ariadne-api/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | ... 3 | } 4 | 5 | type Film { 6 | ... 7 | } 8 | 9 | type Planet { 10 | ... 11 | } 12 | -------------------------------------------------------------------------------- /nltk/README.md: -------------------------------------------------------------------------------- 1 | # NLTK Dojo 2 | 3 | ## Introduction 4 | 5 | This is the directory for the NLTK dojo session happening next November 28th. 6 | This dojo session is based in work done in a [hackathon project developed at Devex](https://github.com/Devex/research) last month. 7 | 8 | ## Setup 9 | 10 | ### With Conda 11 | 12 | For setting this dojo up with [Conda](http://conda.pydata.org/miniconda.html), you can use this command: 13 | 14 | conda env create -f environment.yml 15 | 16 | ### With virtual_env 17 | 18 | For setting this dojo up with [VirtualEnv](https://virtualenv.readthedocs.org/en/latest/), you can use this command: 19 | 20 | ```bash 21 | # create and activate your virtual environment 22 | virtualenv dojo-nltk --python=/usr/bin/python3 23 | source dojo-nltk/bin/activate 24 | 25 | # install requirements 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | To exit virtualenv: `deactivate` 30 | 31 | ### Resources 32 | 33 | [NLTK](http://www.nltk.org/) 34 | A [simple introduction to Naive Bayes classification](http://www.laurentluce.com/posts/twitter-sentiment-analysis-using-python-and-nltk/), using NLTK to classify tweets by sentiment (postive/negative). 35 | 36 | TODO! 37 | -------------------------------------------------------------------------------- /nltk/environment.yml: -------------------------------------------------------------------------------- 1 | name: nltk 2 | dependencies: 3 | - appnope=0.1.0=py34_0 4 | - beautifulsoup4=4.4.1=py34_0 5 | - decorator=4.0.4=py34_0 6 | - freetype=2.5.5=0 7 | - ipykernel=4.1.1=py34_0 8 | - ipython=4.0.0=py34_1 9 | - ipython_genutils=0.1.0=py34_0 10 | - ipywidgets=4.1.0=py34_0 11 | - jinja2=2.8=py34_0 12 | - jsonschema=2.4.0=py34_0 13 | - jupyter=1.0.0=py34_0 14 | - jupyter_client=4.1.1=py34_0 15 | - jupyter_console=4.0.3=py34_0 16 | - jupyter_core=4.0.6=py34_0 17 | - libpng=1.6.17=0 18 | - markupsafe=0.23=py34_0 19 | - mistune=0.7.1=py34_0 20 | - nbconvert=4.0.0=py34_0 21 | - nbformat=4.0.1=py34_0 22 | - nltk=3.1=py34_0 23 | - notebook=4.0.6=py34_0 24 | - numpy=1.10.1=py34_0 25 | - openssl=1.0.2d=0 26 | - path.py=8.1.2=py34_0 27 | - pexpect=3.3=py34_0 28 | - pickleshare=0.5=py34_0 29 | - pip=7.1.2=py34_0 30 | - psycopg2=2.6.1=py34_0 31 | - ptyprocess=0.5=py34_0 32 | - pygments=2.0.2=py34_0 33 | - pyqt=4.11.4=py34_0 34 | - python=3.4.3=2 35 | - python.app=1.2=py34_4 36 | - pyyaml=3.11=py34_1 37 | - pyzmq=14.7.0=py34_1 38 | - qt=4.8.7=1 39 | - qtconsole=4.1.0=py34_0 40 | - readline=6.2=2 41 | - setuptools=18.4=py34_0 42 | - simplegeneric=0.8.1=py34_0 43 | - sip=4.16.9=py34_0 44 | - six=1.10.0=py34_0 45 | - sqlalchemy=1.0.9=py34_0 46 | - sqlite=3.8.4.1=1 47 | - terminado=0.5=py34_1 48 | - tk=8.5.18=0 49 | - tornado=4.2.1=py34_1 50 | - traitlets=4.0.0=py34_0 51 | - wheel=0.26.0=py34_1 52 | - xz=5.0.5=0 53 | - yaml=0.1.6=0 54 | - zeromq=4.1.3=0 55 | - zlib=1.2.8=0 56 | - pip: 57 | - ipython-genutils==0.1.0 58 | - jupyter-client==4.1.1 59 | - jupyter-console==4.0.3 60 | - jupyter-core==4.0.6 61 | - powerline-status==2.2 62 | -------------------------------------------------------------------------------- /nltk/gen_devex_corpus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import json 4 | from sqlalchemy import create_engine 5 | 6 | 7 | def get_connection(): 8 | host = os.environ.get('KW_DB_HOST', 'localhost') 9 | user = os.environ.get('KW_DB_USER', 'me') 10 | password = os.environ.get('KW_DB_PASS', 'somepassword') 11 | connection_string = 'postgresql://{}:{}@{}/neo_production' 12 | engine = create_engine(connection_string.format(user, password, host)) 13 | return engine.connect() 14 | 15 | 16 | def get_contract_descriptions(n_comps=10, n_conts=1000): 17 | connection = get_connection() 18 | contracts_companies = connection.execute(''' 19 | SELECT c_b.company_name as company_name, 20 | c.description_raw as contract_desc 21 | FROM contracts c JOIN bidders b ON b.contract_id = c.id 22 | JOIN (SELECT b.company_id as c_id, com.name as company_name, 23 | COUNT(b.contract_id) as bidded_contracts 24 | FROM bidders b JOIN companies com ON b.company_id = com.id 25 | WHERE b.is_contract_awardee = true 26 | GROUP BY c_id, com.name 27 | ORDER BY bidded_contracts DESC LIMIT {} 28 | ) c_b ON c_id = b.company_id 29 | WHERE c.description_raw IS NOT NULL 30 | AND b.is_contract_awardee = true LIMIT {}'''.format(n_comps, n_conts)) 31 | results = [] 32 | for row in contracts_companies: 33 | results.append((row['contract_desc'], row['company_name'])) 34 | return results 35 | 36 | 37 | def get_corpus_fh(): 38 | corpus_dir = os.path.join('.', 'tmp') 39 | if not os.path.exists(corpus_dir): 40 | os.makedirs(corpus_dir) 41 | corpus_file = os.path.join(corpus_dir, 'devex-corpus.json') 42 | return open(corpus_file,'w') 43 | 44 | 45 | if __name__ == '__main__': 46 | corpus_fh = get_corpus_fh() 47 | contract_descriptions = get_contract_descriptions(10, 2500) 48 | json.dump(contract_descriptions, corpus_fh) 49 | -------------------------------------------------------------------------------- /nltk/tareas.md: -------------------------------------------------------------------------------- 1 | ## Pasos para hacer Naive Bayes 2 | 3 | 1. tokenización 4 | 2. filtrado 5 | 3. normalización 6 | - stemming/lemmatization 7 | 4. entrenamiento 8 | 5. evaluación 9 | 10 | ## Tareas 11 | 12 | 1. [COMPLETADA] Extraer corpus a json. 13 | 2. Explicación Naive Bayes, pasos, ejemplo keyword-corpus de Devex. 14 | 3. Primer pomodoro: Ejemplo propio con código básico: 15 | - Código 16 | - Carga del corpus. 17 | - Función para tokenizar. 18 | - Función para preprocesar. (inicialmente, que no haga nada) 19 | - Clasificador. 20 | 21 | - "Test": 22 | - Input 23 | - texto (ya tokenizado?) 24 | - función para preprocesado: 25 | - normalización (stemming/lemmatization) 26 | - filtrado 27 | - clasificador 28 | - Proceso: 29 | 1. training 30 | 2. testing 31 | 32 | - Output: porcentaje de acierto 33 | 4. Segundo pomodoro: Añadir variabilidad en las funciones de preprocesado. 34 | - ¿Qué otros preprocesados se pueden hacer? 35 | - Tokenizar de forma diferente? 36 | - Stemming, lemmatization? 37 | - Diferentes filtrados 38 | 5. Tercer pomodoro: Añadir variabilidad en la elección del clasificador. 39 | - ¿Qué más clasificadores hay (aparte de NB)? 40 | 6. Cuarto pomodoro: Obtener el mejor resultado. 41 | -------------------------------------------------------------------------------- /optimization/join.py: -------------------------------------------------------------------------------- 1 | def join(loops): 2 | l = [] 3 | for _ in range(loops): 4 | l.append("a") 5 | return "".join(l) 6 | 7 | if __name__ == "__main__": 8 | s = join(int(1e6)) 9 | print("{}".format(len(s))) 10 | -------------------------------------------------------------------------------- /optimization/measure_join.py: -------------------------------------------------------------------------------- 1 | import join 2 | 3 | def measure_join(): 4 | s = join.join(int(1e6)) 5 | print("{}".format(len(s))) 6 | 7 | if __name__ == "__main__": 8 | measure_join() 9 | -------------------------------------------------------------------------------- /pos/README.md: -------------------------------------------------------------------------------- 1 | Kata base 2 | ========= 3 | 4 | Crear un TPV haciendo mocking del almacen: 5 | 6 | 1. Comprobar que el producto este en el almacen. 7 | 2. Comprobar que hay suficientes unidades. 8 | -------------------------------------------------------------------------------- /pos/kata.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/pos/kata.py -------------------------------------------------------------------------------- /pos/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestKata(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /pyzmq-workshop/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | pyzmq = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.6" 13 | -------------------------------------------------------------------------------- /pyzmq-workshop/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "e5769c526fe6d68f9c470853344629f761cebfc1b37267551a4f0c7d841c492d" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "pyzmq": { 20 | "hashes": [ 21 | "sha256:25a0715c8f69cf72f67cfe5a68a3f3ed391c67c063d2257bec0fe7fc2c7f08f8", 22 | "sha256:2bab63759632c6b9e0d5bf19cc63c3b01df267d660e0abcf230cf0afaa966349", 23 | "sha256:30ab49d99b24bf0908ebe1cdfa421720bfab6f93174e4883075b7ff38cc555ba", 24 | "sha256:32c7ca9fc547a91e3c26fc6080b6982e46e79819e706eb414dd78f635a65d946", 25 | "sha256:41219ae72b3cc86d97557fe5b1ef5d1adc1057292ec597b50050874a970a39cf", 26 | "sha256:4b8c48a9a13cea8f1f16622f9bd46127108af14cd26150461e3eab71e0de3e46", 27 | "sha256:55724997b4a929c0d01b43c95051318e26ddbae23565018e138ae2dc60187e59", 28 | "sha256:65f0a4afae59d4fc0aad54a917ab599162613a761b760ba167d66cc646ac3786", 29 | "sha256:6f88591a8b246f5c285ee6ce5c1bf4f6bd8464b7f090b1333a446b6240a68d40", 30 | "sha256:75022a4c60dcd8765bb9ca32f6de75a0ec83b0d96e0309dc479f4c7b21f26cb7", 31 | "sha256:76ea493bfab18dcb090d825f3662b5612e2def73dffc196d51a5194b0294a81d", 32 | "sha256:7b60c045b80709e4e3c085bab9b691e71761b44c2b42dbb047b8b498e7bc16b3", 33 | "sha256:8e6af2f736734aef8ed6f278f9f552ec7f37b1a6b98e59b887484a840757f67d", 34 | "sha256:9ac2298e486524331e26390eac14e4627effd3f8e001d4266ed9d8f1d2d31cce", 35 | "sha256:9ba650f493a9bc1f24feca1d90fce0e5dd41088a252ac9840131dfbdbf3815ca", 36 | "sha256:a02a4a385e394e46012dc83d2e8fd6523f039bb52997c1c34a2e0dd49ed839c1", 37 | "sha256:a3ceee84114d9f5711fa0f4db9c652af0e4636c89eabc9b7f03a3882569dd1ed", 38 | "sha256:a72b82ac1910f2cf61a49139f4974f994984475f771b0faa730839607eeedddf", 39 | "sha256:ab136ac51027e7c484c53138a0fab4a8a51e80d05162eb7b1585583bcfdbad27", 40 | "sha256:c095b224300bcac61e6c445e27f9046981b1ac20d891b2f1714da89d34c637c8", 41 | "sha256:c5cc52d16c06dc2521340d69adda78a8e1031705924e103c0eb8fc8af861d810", 42 | "sha256:d612e9833a89e8177f8c1dc68d7b4ff98d3186cd331acd616b01bbdab67d3a7b", 43 | "sha256:e828376a23c66c6fe90dcea24b4b72cd774f555a6ee94081670872918df87a19", 44 | "sha256:e9767c7ab2eb552796440168d5c6e23a99ecaade08dda16266d43ad461730192", 45 | "sha256:ebf8b800d42d217e4710d1582b0c8bff20cdcb4faad7c7213e52644034300924" 46 | ], 47 | "index": "pypi", 48 | "version": "==17.1.2" 49 | } 50 | }, 51 | "develop": {} 52 | } 53 | -------------------------------------------------------------------------------- /pyzmq-workshop/cities.txt: -------------------------------------------------------------------------------- 1 | Barcelona 2 | Berlín 3 | Madrid 4 | New York 5 | Londres 6 | Igualada 7 | -------------------------------------------------------------------------------- /pyzmq-workshop/dojos.css: -------------------------------------------------------------------------------- 1 | section p { 2 | text-align: left; 3 | } 4 | 5 | pre#src-python{ 6 | background-color:black; 7 | } 8 | 9 | .figure p { 10 | text-align: center; 11 | } 12 | 13 | section pre { 14 | background-color: white; 15 | color: black; 16 | } 17 | -------------------------------------------------------------------------------- /pyzmq-workshop/helloworld_client.py: -------------------------------------------------------------------------------- 1 | import time 2 | import zmq 3 | 4 | context = zmq.Context() 5 | socket = context.socket(zmq.REQ) 6 | socket.connect("tcp://localhost:5555") 7 | 8 | for request in range(10): 9 | print('Sending request {}...'.format(request)) 10 | socket.send(b'Hello') 11 | response = socket.recv().decode() 12 | print('Received reply {}: {}'.format(request, response)) 13 | -------------------------------------------------------------------------------- /pyzmq-workshop/helloworld_server.py: -------------------------------------------------------------------------------- 1 | import time 2 | import zmq 3 | 4 | context = zmq.Context() 5 | server = context.socket(zmq.REP) 6 | server.bind('tcp://*:5555') 7 | 8 | while True: 9 | message = server.recv().decode() 10 | print('Received request: {}'.format(message)) 11 | time.sleep(1) 12 | server.send(b'World') 13 | -------------------------------------------------------------------------------- /pyzmq-workshop/hider.py: -------------------------------------------------------------------------------- 1 | import random 2 | import zmq 3 | 4 | cities = open('cities.txt').readlines() 5 | city = random.choice(cities).strip() 6 | print('[HIDER] City chosen is {}'.format(city)) 7 | 8 | context = zmq.Context() 9 | server = context.socket(zmq.REP) 10 | server.bind('tcp://*:5555') 11 | 12 | while True: 13 | message = server.recv().decode() 14 | print('[HIDER] Received request: {}'.format(message)) 15 | print('[HIDER] Current city: {}'.format(city)) 16 | reply = 'INCORRECT' 17 | if message == city: 18 | reply = 'CORRECT' 19 | city = random.choice(cities).strip() 20 | print('[HIDER] City chosen is {}'.format(city)) 21 | server.send(str.encode(reply)) 22 | -------------------------------------------------------------------------------- /pyzmq-workshop/pub.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | import time 3 | import sys 4 | 5 | port = "5556" 6 | if len(sys.argv) > 1: 7 | port = sys.argv[1] 8 | int(port) 9 | 10 | context = zmq.Context() 11 | socket = context.socket(zmq.PUB) 12 | socket.bind("tcp://*:%s" % port) 13 | topic = 0 14 | while True: 15 | topic, now = topic + 1, time.ctime() 16 | socket.send_string("1 update {} {}".format(topic, now)) 17 | socket.send_string("2 update {} {}".format(topic, now)) 18 | time.sleep(1) 19 | -------------------------------------------------------------------------------- /pyzmq-workshop/seeker.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | import zmq 4 | 5 | cities = open('cities.txt').readlines() 6 | 7 | context = zmq.Context() 8 | socket = context.socket(zmq.REQ) 9 | socket.connect('tcp://localhost:5555') 10 | 11 | response = '' 12 | while response != 'CORRECT': 13 | city = random.choice(cities).strip() 14 | print('[SEEKER] Guessed city is {}'.format(city)) 15 | socket.send(str.encode(city)) 16 | response = socket.recv().decode() 17 | print('[SEEKER] Received reply {}: {}'.format(city, response)) 18 | -------------------------------------------------------------------------------- /pyzmq-workshop/sub.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import zmq 3 | 4 | context = zmq.Context() 5 | sock = context.socket(zmq.SUB) 6 | 7 | topic = "1" 8 | if len(sys.argv) > 1: 9 | topic = sys.argv[1] 10 | sock.setsockopt_string(zmq.SUBSCRIBE, topic) 11 | sock.connect("tcp://127.0.0.1:5556") 12 | 13 | while True: 14 | message = sock.recv_string() 15 | print(message) 16 | -------------------------------------------------------------------------------- /pyzmq-workshop/weather_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import zmq 3 | 4 | port = "5556" 5 | if len(sys.argv) > 1: 6 | port = sys.argv[1] 7 | int(port) 8 | 9 | if len(sys.argv) > 2: 10 | port1 = sys.argv[2] 11 | int(port1) 12 | 13 | # Socket to talk to server 14 | context = zmq.Context() 15 | socket = context.socket(zmq.SUB) 16 | 17 | print("Collecting updates from weather server...") 18 | socket.connect ("tcp://localhost:%s" % port) 19 | 20 | if len(sys.argv) > 2: 21 | socket.connect ("tcp://localhost:%s" % port1) 22 | 23 | # Subscribe to zipcode, default is NYC, 10001 24 | topicfilter = "10001" 25 | socket.setsockopt_string(zmq.SUBSCRIBE, topicfilter) 26 | 27 | # Process 5 updates 28 | total_value = 0 29 | for update_nbr in range (5): 30 | string = socket.recv() 31 | topic, messagedata = string.split() 32 | total_value += int(messagedata) 33 | 34 | print("Avg temp for zip '%s' was %dF" % (topicfilter, total_value / update_nbr)) 35 | -------------------------------------------------------------------------------- /pyzmq-workshop/weather_server.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | import random 3 | import sys 4 | import time 5 | 6 | port = "5556" 7 | if len(sys.argv) > 1: 8 | port = sys.argv[1] 9 | int(port) 10 | 11 | context = zmq.Context() 12 | socket = context.socket(zmq.PUB) 13 | socket.bind("tcp://*:%s" % port) 14 | while True: 15 | topic = random.randrange(9999,10005) 16 | messagedata = random.randrange(1,215) - 80 17 | socket.send_string("%d %d" % (topic, messagedata)) 18 | time.sleep(1) 19 | -------------------------------------------------------------------------------- /sqlalchemy/db/books.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/sqlalchemy/db/books.db -------------------------------------------------------------------------------- /sqlalchemy/db/books.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=OFF; 2 | BEGIN TRANSACTION; 3 | CREATE TABLE authors ( id integer primary key autoincrement, name text not nul 4 | l, birth date); 5 | INSERT INTO authors VALUES(1,'John','1980-12-25'); 6 | INSERT INTO authors VALUES(2,'Mike','1976-9-4'); 7 | CREATE TABLE books (id integer primary key autoincrement, title text not null, 8 | author_id integer not null, published_in date, foreign key (author_id) refere 9 | nces authors(id)); 10 | INSERT INTO books VALUES(1,'My life with Anna',1,'2004-3-6'); 11 | INSERT INTO books VALUES(2,'Bye, bye, Anna',1,'2004-6-6'); 12 | INSERT INTO books VALUES(3,'Wonderful Anna',2,'2005-1-2'); 13 | DELETE FROM sqlite_sequence; 14 | INSERT INTO sqlite_sequence VALUES('authors',2); 15 | INSERT INTO sqlite_sequence VALUES('books',3); 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/sqlalchemy/no_orm/__init__.py -------------------------------------------------------------------------------- /sqlalchemy/no_orm/add_author.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from author import Author 3 | import sqlite3 4 | import sys 5 | 6 | if len(sys.argv) != 3: 7 | print("ERROR: Author's name and date of birth (YYYY-MM-DD) must be specified") 8 | sys.exit(1) 9 | 10 | conn = sqlite3.connect('../db/books.db') 11 | new_author = Author(None, sys.argv[1], sys.argv[2]) 12 | new_author.save(conn) 13 | print( 14 | 'Author {}, born on {}, inserted with id {}'.format( 15 | new_author.name, 16 | new_author.birth, 17 | new_author.id, 18 | )) 19 | conn.close() 20 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/add_book.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from book import Book 3 | import sqlite3 4 | import sys 5 | 6 | if len(sys.argv) != 4: 7 | print("ERROR: Book's title, author name, and date of publishing (YYYY-MM-DD) must be specified") 8 | sys.exit(1) 9 | 10 | conn = sqlite3.connect('../db/books.db') 11 | new_book = Book(None, sys.argv[1], sys.argv[2], sys.argv[3]) 12 | new_book.save(conn) 13 | print( 14 | 'Book {}, published in {}, written by {}, inserted with id {}'.format( 15 | new_book.title, 16 | new_book.published_in, 17 | new_book.author_id, 18 | new_book.id, 19 | )) 20 | conn.close() 21 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/author.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | class Author(object): 4 | def __init__(self, author_id, name, birth): 5 | self.id = author_id 6 | self.name = name 7 | self.birth = birth 8 | 9 | def save(self, conn): 10 | if self.id is None: 11 | conn.execute( 12 | 'INSERT INTO authors VALUES(?, ?, ?)', 13 | ( 14 | self.id, 15 | self.name, 16 | self.birth, 17 | ), 18 | ) 19 | conn.commit() 20 | self.id = conn.execute('SELECT last_insert_rowid()').fetchone()[0] 21 | else: 22 | conn.execute( 23 | 'UPDATE authors SET name = ?, birth = ? WHERE id = ?', 24 | ( 25 | self.name, 26 | self.birth, 27 | self.id, 28 | ), 29 | ) 30 | conn.commit() 31 | 32 | def delete(self,conn): 33 | conn.execute( 34 | '''DELETE FROM authors WHERE id = ?''', 35 | (self.id,), 36 | ) 37 | conn.commit() 38 | 39 | @property 40 | def birth(self): 41 | return self.__birth.date() 42 | 43 | @birth.setter 44 | def birth(self, birth): 45 | self.__birth = datetime.strptime(birth, '%Y-%m-%d') 46 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/authors.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from author import Author 3 | 4 | class Authors(object): 5 | def __init__(self, criteria=None): 6 | if criteria is None: 7 | self.__criteria__ = [] 8 | else: 9 | self.__criteria__ = criteria 10 | 11 | def select(self, conn): 12 | cursor = conn.cursor() 13 | if len(self.__criteria__) > 0: 14 | if self.__criteria__[0] == "published_in": 15 | cursor.execute(''' 16 | SELECT id, name, birth 17 | FROM authors 18 | WHERE id in ( 19 | SELECT author_id 20 | FROM books 21 | WHERE date(published_in) BETWEEN date(?) AND date(?) 22 | ) 23 | ''', ( 24 | self.__criteria__[1], 25 | self.__criteria__[2] 26 | ), 27 | ) 28 | else: 29 | cursor.execute( 30 | '''SELECT * FROM authors WHERE {} {} ?'''.format( 31 | self.__criteria__[0], 32 | self.__criteria__[1], 33 | ), 34 | ( 35 | self.__criteria__[2], 36 | ), 37 | ) 38 | else: 39 | cursor.execute('SELECT * FROM authors') 40 | return cursor 41 | 42 | def update(self, conn, field, new_value): 43 | for row in self.select(conn): 44 | author = Author( 45 | row['id'], 46 | row['name'], 47 | row['birth'], 48 | ) 49 | setattr(author, field, new_value) 50 | author.save(conn) 51 | 52 | def list(self, conn): 53 | for row in self.select(conn): 54 | for member in row: 55 | print(member, end=" ") 56 | print("") 57 | 58 | def delete(self, conn): 59 | for row in self.select(conn): 60 | author = Author( 61 | row['id'], 62 | row['name'], 63 | row['birth'], 64 | ) 65 | author.delete(conn) 66 | print("") 67 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/book.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from authors import Authors 3 | import sqlite3 4 | 5 | class Book(object): 6 | def __init__(self, book_id, title, author_name, published_in): 7 | self.id = book_id 8 | self.title = title 9 | if not author_name is None: 10 | conn = sqlite3.connect('../db/books.db') 11 | authors = Authors(['name', '=', author_name]) 12 | self.author_id = authors.select(conn).fetchone()[0] 13 | conn.close() 14 | self.published_in = published_in 15 | 16 | def save(self, conn): 17 | if self.id is None: 18 | conn.execute( 19 | 'INSERT INTO books VALUES(?, ?, ?, ?)', 20 | ( 21 | self.id, 22 | self.title, 23 | self.author_id, 24 | self.published_in, 25 | ), 26 | ) 27 | conn.commit() 28 | self.id = conn.execute('SELECT last_insert_rowid()').fetchone()[0] 29 | else: 30 | conn.execute( 31 | 'UPDATE books SET title = ?, author_id = ?, published_in = ? WHERE id = ?', 32 | ( 33 | self.title, 34 | self.author_id, 35 | self.published_in, 36 | self.id, 37 | ), 38 | ) 39 | conn.commit() 40 | 41 | def delete(self,conn): 42 | conn.execute( 43 | '''DELETE FROM books WHERE id = ?''', 44 | (self.id,), 45 | ) 46 | conn.commit() 47 | 48 | @property 49 | def published_in(self): 50 | return self.__published_in.date() 51 | 52 | @published_in.setter 53 | def published_in(self, published_in): 54 | self.__published_in = datetime.strptime(published_in, '%Y-%m-%d') 55 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/books.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from book import Book 3 | from authors import Authors 4 | 5 | class Books(object): 6 | def __init__(self, criteria=None): 7 | if criteria is None: 8 | self.__criteria__ = [] 9 | else: 10 | self.__criteria__ = criteria 11 | 12 | def select(self, conn): 13 | cursor = conn.cursor() 14 | if len(self.__criteria__) > 0: 15 | if self.__criteria__[0] == 'birth': 16 | cursor.execute(''' 17 | SELECT b.id, title, name, birth, published_in 18 | FROM books b 19 | JOIN authors a 20 | ON b.author_id = a.id 21 | WHERE date(birth) BETWEEN date(?) AND date(?) 22 | ''', ( 23 | self.__criteria__[1], 24 | self.__criteria__[2] 25 | ) 26 | ) 27 | else: 28 | cursor.execute(''' 29 | SELECT b.id, title, name, published_in 30 | FROM books b 31 | JOIN authors a 32 | ON b.author_id = a.id 33 | WHERE {} {} ? 34 | '''.format( 35 | self.__criteria__[0], 36 | self.__criteria__[1], 37 | ), 38 | ( 39 | self.__criteria__[2], 40 | ), 41 | ) 42 | else: 43 | cursor.execute(''' 44 | SELECT b.id, title, name, published_in 45 | FROM books b 46 | JOIN authors a 47 | ON b.author_id == a.id 48 | ''') 49 | return cursor 50 | 51 | def update(self, conn, field, new_value): 52 | for row in self.select(conn): 53 | author_name = Authors(['name', '=', row[2]]).select(conn).fetchone()[1] 54 | if field == "name": 55 | author_name = new_value 56 | book = Book( 57 | row['id'], 58 | row['title'], 59 | author_name, 60 | row['published_in'], 61 | ) 62 | setattr(book, field, new_value) 63 | book.save(conn) 64 | 65 | def list(self, conn): 66 | for row in self.select(conn): 67 | for member in row: 68 | print(member, end=" ") 69 | print("") 70 | 71 | def delete(self, conn): 72 | for row in self.select(conn): 73 | book = Book( 74 | row['id'], 75 | row['title'], 76 | None, 77 | row['published_in'], 78 | ) 79 | book.delete(conn) 80 | print("") 81 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/del_authors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from authors import Authors 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) == 1: 9 | authors = Authors() 10 | elif len(sys.argv) == 4: 11 | authors = Authors(sys.argv[1:4]) 12 | else: 13 | print("ERROR: Either field, operator and value or empty arguments can be specified.") 14 | conn.close() 15 | sys.exit(1) 16 | 17 | authors.delete(conn) 18 | conn.close() 19 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/del_books.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from books import Books 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) == 1: 9 | books = Books() 10 | elif len(sys.argv) == 4: 11 | books = Books(sys.argv[1:4]) 12 | else: 13 | print("ERROR: Either field, operator and value or empty arguments can be specified.") 14 | conn.close() 15 | sys.exit(1) 16 | 17 | books.delete(conn) 18 | conn.close() 19 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/list_authors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from authors import Authors 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) == 1: 9 | authors = Authors() 10 | elif len(sys.argv) == 4: 11 | authors = Authors(sys.argv[1:4]) 12 | else: 13 | print("ERROR: Either field, operator and value or empty arguments can be specified.") 14 | conn.close() 15 | sys.exit(1) 16 | 17 | authors.list(conn) 18 | conn.close() 19 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/list_books.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from books import Books 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) == 1: 9 | books = Books() 10 | elif len(sys.argv) == 4: 11 | books = Books(sys.argv[1:4]) 12 | else: 13 | print("ERROR: Either field, operator and value or empty arguments can be specified.") 14 | conn.close() 15 | sys.exit(1) 16 | 17 | books.list(conn) 18 | conn.close() 19 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/update_authors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from authors import Authors 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) != 6: 9 | print("ERROR: Search field, operator, search value, updated field, and new value must be specified.") 10 | conn.close() 11 | sys.exit(1) 12 | else: 13 | authors = Authors(sys.argv[1:4]) 14 | 15 | authors.update(conn, sys.argv[4], sys.argv[5]) 16 | conn.close() 17 | -------------------------------------------------------------------------------- /sqlalchemy/no_orm/update_books.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from books import Books 3 | import sqlite3 4 | import sys 5 | 6 | conn = sqlite3.connect('../db/books.db') 7 | conn.row_factory = sqlite3.Row 8 | if len(sys.argv) != 6: 9 | print("ERROR: Search field, operator, search value, updated field, and new value must be specified.") 10 | conn.close() 11 | sys.exit(1) 12 | else: 13 | books = Books(sys.argv[1:4]) 14 | 15 | books.update(conn, sys.argv[4], sys.argv[5]) 16 | conn.close() 17 | -------------------------------------------------------------------------------- /sqlalchemy/notes.org: -------------------------------------------------------------------------------- 1 | #+REVEAL_HLEVEL: 2 2 | #+TITLE: Introduction to SQLAlchemy 3 | #+AUTHOR: Ricardo Martinez, Ignasi Fosch 4 | * Why ORM? 5 | ** What is a ORM? 6 | ORM, meaning Object Relationship Mapping, is a library providing an abstraction layer over a database and its structure. 7 | 8 | ** Features 9 | A ORM provides the following: 10 | * Database engine abstraction 11 | * Query language abstraction, at least about the details 12 | * Work with objects instead of records, tables, raw data. 13 | 14 | ** Discussion 15 | *** http://opensourceforu.com/2009/05/database-programming-in-python/ 16 | 17 | *** http://www.yegor256.com/2014/12/01/orm-offensive-anti-pattern.html 18 | 19 | *** https://www.reddit.com/r/Python/comments/5fyr32/orm_or_not/ 20 | 21 | *** https://www.fullstackpython.com/object-relational-mappers-orms.html 22 | 23 | *** http://www.agiledata.org/essays/impedanceMismatch.html 24 | 25 | *** http://www.yegor256.com/code-ahead.html 26 | 27 | *** https://martinfowler.com/bliki/OrmHate.html 28 | 29 | *** https://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science/ 30 | 31 | *** http://blogs.tedneward.com/post/the-vietnam-of-computer-science/ 32 | 33 | *** http://seldo.com/weblog/2011/08/11/orm_is_an_antipattern 34 | 35 | *** http://techblog.bozho.net/orm-haters-dont-get-it/ 36 | 37 | *** http://docs.python-guide.org/en/latest/scenarios/db/ 38 | 39 | * Exercise description 40 | In this session, we'll work on a very simple application for libraries, providing basic operations on their books. 41 | The underlying database will have the following tables: 42 | ** Authors: 43 | | Field | Description | 44 | | id | Identifier for every record | 45 | | name | Name of the Author | 46 | | birth | Author's date of birth | 47 | ** Books: 48 | | Field | Description | 49 | | id | Identifier for every record | 50 | | title | Title of the book | 51 | | author_id | Id of this book's author | 52 | | published_in | Date this book was published | 53 | ** Requirements to implement 54 | The program we've been requested to write has the following features: 55 | * Add, remove, update, and list authors 56 | * Add, remove, and update books 57 | * List books, which should show the author's name 58 | * Search for books which authors were born within a range of dates 59 | * Search for authors who published books within a range of years 60 | * List all books published by an author 61 | 62 | To be time-savvy, we're providing a sqlite database with this structure already setup, and with a few example records. 63 | 64 | * Example without SQLAlchemy 65 | 66 | * Tool with SQLAlchemy 67 | 68 | * Second iteration 69 | *** Implement the same system, but with multiple authors per book 70 | -------------------------------------------------------------------------------- /sqlalchemy/slides.org: -------------------------------------------------------------------------------- 1 | #+REVEAL_HLEVEL: 1 2 | #+TITLE: Introduction to SQLAlchemy 3 | #+AUTHOR: Ricardo Martinez, Ignasi Fosch 4 | #+OPTIONS: toc:2 5 | * Why ORM? 6 | ** What is a ORM? 7 | **** ORM, meaning Object-relational mapping 8 | **** is a technique enabling to access a RDBMS from an OO language, or style. 9 | **** Different implementations through different patterns: Active Record, DAO,... 10 | **** It can refer to the tools (mappers) enabling applying this technique 11 | 12 | ** Features 13 | **** Database engine abstraction layer (vs DB-API) 14 | **** Query language abstraction layer 15 | **** Work with objects instead of records, tables, scalars 16 | 17 | ** Downsides 18 | **** Impedance mismatch 19 | **** Potential for performance reduction 20 | **** Complexity shift from DB to code 21 | **** Schema migrations off ORM 22 | 23 | ** ORMHate 24 | *** Arguments 25 | **** Speed/low performance (ORM performance vs tuned SQL SP) 26 | **** Database upgrades are hard 27 | **** No need for portability 28 | **** Takes database interaction away from the objects (session factory) 29 | **** SQL, or corresponding dialect, is not hidden 30 | 31 | *** Arguments (II) 32 | **** Difficult to test 33 | **** Time to learn it 34 | **** Attribute creep 35 | **** Foreign keys 36 | **** Data retrieval (window functions) 37 | **** Dual schema 38 | **** Identities 39 | 40 | *** Alternative 41 | **** SQL-speaking objects 42 | 43 | *** From replies 44 | **** Not all implementations fit the same use cases 45 | **** Most general cases are well covered, specially the complex huge ERs... 46 | **** but some situations may require more work 47 | **** Tradeoffs always depend on PoV 48 | 49 | ** Links 50 | **** [[http://www.yegor256.com/2014/12/01/orm-offensive-anti-pattern.html][ORM being an anti-pattern]] 51 | **** [[https://medium.com/@mantasd/orm-is-an-offensive-anti-pattern-really-42269673d54d][ORM is an Offensive anti-pattern. Really?]] 52 | **** [[https://www.reddit.com/r/Python/comments/5fyr32/orm_or_not/][Discussion about using SQLAlchemy]] 53 | **** [[https://www.fullstackpython.com/object-relational-mappers-orms.html][Article about ORMs in Python]] 54 | **** [[http://www.agiledata.org/essays/impedanceMismatch.html][Object-Relational Impedance mismatch]] 55 | **** [[https://martinfowler.com/bliki/OrmHate.html][ORMHate]] 56 | **** [[https://maetl.net/talks/rise-and-fall-of-orm][The rise and fall of ORM]] 57 | **** [[http://woz.posthaven.com/what-orms-have-taught-me-just-learn-sql][What ORMs have taught me: Just learn SQL]] 58 | **** [[http://blogs.tedneward.com/post/the-vietnam-of-computer-science/][The Vietnam of Computer Science]] 59 | **** [[https://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science/][ORM is the Vietnam of Computer Science]] 60 | **** [[http://seldo.com/weblog/2011/08/11/orm_is_an_antipattern][ORM is an antipattern]] 61 | **** [[http://techblog.bozho.net/orm-haters-dont-get-it/][ORM Haters don't get it]] 62 | 63 | 64 | * Exercise description 65 | In this session, we'll work on a very simple application for libraries, providing basic operations on their books. 66 | The underlying database will have the following tables. 67 | 68 | ** Authors 69 | 70 | | Field | Description | 71 | | id | Identifier for every record | 72 | | name | Name of the Author | 73 | | birth | Author's date of birth | 74 | 75 | ** Books: 76 | 77 | | Field | Description | 78 | | id | Identifier for every record | 79 | | title | Title of the book | 80 | | author_id | Id of this book's author | 81 | | published_in | Date this book was published | 82 | 83 | ** Requirements to implement 84 | The program we've been requested to write has the following features: 85 | * Add, remove, update, and list authors 86 | * Add, remove, and update books 87 | * List books, which should show the author's name 88 | * Search for books which authors were born within a range of dates 89 | * Search for authors who published books within a range of years 90 | * List all books published by an author 91 | 92 | To be time-savvy, we're providing a sqlite database with this structure already setup, and with a few example records. 93 | 94 | 95 | * Example without SQLAlchemy 96 | 97 | * Tool with SQLAlchemy 98 | 99 | * Implement multiple authors per book 100 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/add.py: -------------------------------------------------------------------------------- 1 | from base import Session 2 | from models import table_class_list 3 | 4 | session = Session() 5 | 6 | if __name__ == '__main__': 7 | tables = {index: table for index, table in enumerate(table_class_list)} 8 | 9 | while True: 10 | print('Add records:', '\n') 11 | print('Tables list:', '\n') 12 | print(tables, '\n') 13 | user_response = input('Enter \'table #\' or \'x\' to exit: ') 14 | 15 | if user_response.lower() == 'x' or user_response == '': 16 | break 17 | else: 18 | table = tables.get(int(user_response), None) 19 | 20 | if table is not None: 21 | # Retrieve list of columns 22 | columns = [col.name for col in table.__table__.columns if col.name != 'id'] 23 | print(columns) 24 | 25 | # Initialize a new record 26 | data = {} 27 | 28 | for col in columns: 29 | value = input('type a value for {0}: '.format(col)) 30 | data[col] = value 31 | 32 | if input('Add record? y/n') == 'y': 33 | record = table(**data) 34 | session.add(record) 35 | session.commit() 36 | else: 37 | print('Table not found', '\n') 38 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import sessionmaker 3 | 4 | engine = create_engine('sqlite:///books.db') 5 | Session = sessionmaker(bind=engine) 6 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/base.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/sqlalchemy/sqlalchemy/base.pyc -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/books.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/sqlalchemy/sqlalchemy/books.db -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/delete.py: -------------------------------------------------------------------------------- 1 | from base import Session 2 | from models import table_class_list 3 | from list import list_all 4 | 5 | session = Session() 6 | 7 | if __name__ == '__main__': 8 | tables = {index: table for index, table in enumerate(table_class_list)} 9 | 10 | while True: 11 | print('Delete records:', '\n') 12 | print('Tables list:', '\n') 13 | print(tables, '\n') 14 | user_response = input('Enter \'table #\' or \'x\' to exit: ') 15 | 16 | if user_response.lower() == 'x' or user_response == '': 17 | break 18 | else: 19 | # Get the Table Class 20 | table = tables.get(int(user_response), None) 21 | 22 | if table is not None: 23 | # Query to DB 24 | list_all(table) 25 | 26 | record_id = input('Enter \'record #\' or \'c\' to cancel...') 27 | if record_id.lower() != 'c' and record_id != '': 28 | # Get the Record ID 29 | record = session.query(table).get(record_id) 30 | 31 | if input('Add record? y/n') == 'y' and record is not None: 32 | session.delete(record) 33 | session.commit() 34 | 35 | else: 36 | print('Table not found', '\n') 37 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/filter.py: -------------------------------------------------------------------------------- 1 | from base import Session 2 | from models import table_class_list 3 | 4 | session = Session() 5 | 6 | 7 | if __name__ == '__main__': 8 | tables = {index: table for index, table in enumerate(table_class_list)} 9 | 10 | while True: 11 | print('Filter table:', '\n') 12 | print('Tables list:', '\n') 13 | print(tables, '\n') 14 | user_response = input('Enter \'table #\' or \'x\' to exit: ') 15 | 16 | if user_response.lower() == 'x' or user_response == '': 17 | break 18 | else: 19 | # Get the Table Class 20 | table = tables.get(int(user_response), None) 21 | 22 | if table is not None: 23 | columns = {index: col.name 24 | for index, col in enumerate(table.__table__.columns) 25 | if col.name != 'id'} 26 | 27 | print(columns, '\n') 28 | user_response = input('Enter \'column #\' or \'c\' to cancel: ') 29 | if user_response.lower() == 'c' or user_response == '': 30 | pass 31 | else: 32 | # Get the Column 33 | column = columns.get(int(user_response), None) 34 | criteria = '%' + input('Enter the criteria for [{0}]: '.format(column)) + '%' 35 | 36 | if column is not None: 37 | # Query to DB 38 | print(session.query(table).filter(getattr(table, column, None).like(criteria)).all()) 39 | 40 | input('press any key to continue...') 41 | else: 42 | print('Table not found', '\n') 43 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/insert_test.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from base import Session, engine 3 | from models import Base, Author, Book 4 | 5 | Base.metadata.create_all(engine) 6 | session = Session() 7 | 8 | author_1 = Author(name='J.R.R. Tolkien', birth=date(1892, 1, 3)) 9 | author_2 = Author(name='J.K. Rowling', birth=date(1965, 7, 31)) 10 | author_3 = Author(name='Stephen King', birth=date(1947, 9, 21)) 11 | 12 | book_1 = Book(title='The Hobbit', published_in=date(1937, 9, 21), author_id=1) 13 | book_2 = Book(title='The Lord of the Rings', published_in=date(1954, 7, 29), author_id=1) 14 | book_3 = Book(title='Harry Potter', published_in=date(1997, 6, 26), author_id=2) 15 | book_4 = Book(title='Carrie', published_in=date(1974, 1, 1), author_id=3) 16 | book_5 = Book(title='Salem Lot', published_in=date(1975, 1, 1), author_id=3) 17 | book_6 = Book(title='The Shining', published_in=date(1977, 1, 1), author_id=3) 18 | book_7 = Book(title='Rage', published_in=date(1977, 1, 1), author_id=3) 19 | 20 | session.add(author_1) 21 | session.add(author_2) 22 | session.add(author_3) 23 | 24 | session.add(book_1) 25 | session.add(book_2) 26 | session.add(book_3) 27 | session.add(book_4) 28 | session.add(book_5) 29 | session.add(book_6) 30 | session.add(book_7) 31 | 32 | session.commit() 33 | session.close() 34 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/list.py: -------------------------------------------------------------------------------- 1 | from base import Session 2 | from models import table_class_list 3 | 4 | session = Session() 5 | 6 | 7 | def list_all(table_class): 8 | print(session.query(table_class).all()) 9 | 10 | 11 | if __name__ == '__main__': 12 | tables = {index: table for index, table in enumerate(table_class_list)} 13 | 14 | while True: 15 | print('Tables list:', '\n') 16 | print(tables, '\n') 17 | user_response = input('Enter \'table #\' or \'x\' to exit: ') 18 | 19 | if user_response.lower() == 'x' or user_response == '': 20 | break 21 | else: 22 | # Get the Table Class 23 | table = tables.get(int(user_response), None) 24 | 25 | if table is not None: 26 | # Query to DB 27 | list_all(table) 28 | input('press any key to continue...') 29 | else: 30 | print('Table not found', '\n') 31 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/models.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from sqlalchemy import Column, ForeignKey, Integer, String, Date 3 | from sqlalchemy.orm import relationship, synonym 4 | from sqlalchemy.ext.declarative import declarative_base 5 | 6 | Base = declarative_base() 7 | 8 | 9 | class Author(Base): 10 | __tablename__ = 'authors' 11 | 12 | id = Column(Integer, primary_key=True, nullable=False) 13 | name = Column(String, nullable=False) 14 | _birth = Column(Date) 15 | books = relationship('Book', lazy='joined') 16 | 17 | @property 18 | def birth(self): 19 | return self._birth 20 | 21 | @birth.setter 22 | def birth(self, value): 23 | if type(value) == date: 24 | self._birth = value 25 | else: 26 | localdate = list(map((lambda item: int(item)), value.split('-'))) 27 | self._birth = date(localdate[0], localdate[1], localdate[2]) 28 | 29 | birth = synonym('_birth', descriptor=birth) 30 | 31 | def __init__(self, **kwargs): 32 | birth = kwargs['birth'] 33 | name = kwargs['name'] 34 | 35 | self.name = name 36 | self.birth = birth 37 | 38 | def __repr__(self): 39 | return '\n' \ 40 | .format(self.id, self.name, self.birth, self.books) 41 | 42 | 43 | class Book(Base): 44 | __tablename__ = 'books' 45 | 46 | id = Column(Integer, primary_key=True, nullable=False) 47 | title = Column(String, nullable=False) 48 | _published_in = Column(Date, nullable=True) 49 | author_id = Column(Integer, ForeignKey('authors.id')) 50 | 51 | @property 52 | def published_in(self): 53 | return self._published_in 54 | 55 | @published_in.setter 56 | def published_in(self, value): 57 | if type(value) == date: 58 | self._published_in = value 59 | else: 60 | localdate = list(map((lambda item: int(item)), value.split('-'))) 61 | self._published_in = date(localdate[0], localdate[1], localdate[2]) 62 | 63 | published_in = synonym('_published_in', descriptor=published_in) 64 | 65 | def __init__(self, **kwargs): 66 | title = kwargs['title'] 67 | published_in = kwargs['published_in'] 68 | author_id = kwargs['author_id'] 69 | 70 | self.title = title 71 | self.published_in = published_in 72 | self.author_id = author_id 73 | 74 | def __repr__(self): 75 | return '\n' \ 76 | .format(self.id, self.title, self.published_in, self.author_id) 77 | 78 | 79 | table_class_list = [Author, Book] 80 | -------------------------------------------------------------------------------- /sqlalchemy/sqlalchemy/update.py: -------------------------------------------------------------------------------- 1 | from base import Session 2 | from models import table_class_list 3 | from list import list_all 4 | 5 | session = Session() 6 | 7 | if __name__ == '__main__': 8 | tables = {index: table for index, table in enumerate(table_class_list)} 9 | 10 | while True: 11 | print('Modify records:', '\n') 12 | print('Tables list:', '\n') 13 | print(tables, '\n') 14 | user_response = input('Enter \'table #\' or \'x\' to exit: ') 15 | 16 | if user_response.lower() == 'x' or user_response == '': 17 | break 18 | else: 19 | table = tables.get(int(user_response), None) 20 | 21 | if table is not None: 22 | # Retrieve list of columns 23 | columns = [col.name.replace('_', '') for col in table.__table__.columns if col.name != 'id'] 24 | 25 | # List all records 26 | list_all(table) 27 | 28 | record_id = input('Enter \'record #\' or \'c\' to cancel...') 29 | if record_id.lower() != 'c' and record_id != '': 30 | 31 | if input('Update record? y/n') == 'y' and record_id is not None: 32 | # Get the Record ID 33 | record = session.query(table).get(record_id) 34 | 35 | for col in columns: 36 | value = input('type new value for {0} [{1}]: '.format(col, getattr(record, col, None))) 37 | if value is not None: 38 | setattr(record, col, value) 39 | 40 | session.merge(record) 41 | session.commit() 42 | else: 43 | print('Table not found', '\n') 44 | -------------------------------------------------------------------------------- /string-calculator-mock/README.md: -------------------------------------------------------------------------------- 1 | Kata String Calculator 2 | ====================== 3 | 4 | Introducción 5 | ------------ 6 | 7 | En esta kata, partiremos de otra kata ya resuelta sobre la que desarrollaremos algunas funcionalidades nuevas que nos obligarán a utilizar *mocking* para que los tests puedan comprobarlo. 8 | 9 | Dado que *mocking* es un concepto un poco confuso, la kata consistirá de tres fases. En la primera se hará un ejemplo del uso de `mock` para crear un falso recurso a utilizar. 10 | 11 | Requerimientos 12 | -------------- 13 | 14 | En este caso, para la realización de la kata, será necesario disponer de la librería `mock` instalada. Para instalaros la librería, debería bastar en ejecutar el siguiente comando, estando conectados a Internet: :: 15 | 16 | pip install mock 17 | 18 | Primera fase 19 | ------------ 20 | 21 | El objetivo de esta primera fase es crear un nuevo programa que, utilizando la clase StringCalculator, permitirá al usuario invocarlo con los dígitos desde la línea de comandos, de la siguiente forma: :: 22 | 23 | stringcal "12,4,5" 24 | 25 | Segunda fase 26 | ------------ 27 | 28 | En la segunda fase, el programa responderá al usuario imprimiendo la suma, el resultado, por la pantalla: :: 29 | 30 | stringcal "12,4,5" 31 | 21 32 | 33 | Tercera fase 34 | ------------ 35 | 36 | Una vez impreso el resultado, el programa imprimirá la pregunta `Otra entrada, por favor` y esperará otra lista de usuarios que el usuario deberá entrar. Si el usuario pulsa Enter sin ninguna entrada, el programa saldrá. 37 | -------------------------------------------------------------------------------- /string-calculator-mock/StringCalculator.py: -------------------------------------------------------------------------------- 1 | class StringCalculator(object): 2 | def Add(self, operands): 3 | delimiter = ',' 4 | first_line = operands.split('\n')[0] 5 | if first_line.startswith('//'): 6 | delimiter = first_line.split('//')[1] 7 | operands = '\n'.join(operands.split('\n')[1:]) 8 | return sum([int('0' + x) for x in operands.replace(delimiter, ',').replace('\n', ',').split(',')]) 9 | -------------------------------------------------------------------------------- /string-calculator-mock/StringCalculator_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from StringCalculator import * 5 | 6 | class TestKata(unittest.TestCase): 7 | def setUp(self): 8 | self.calc = StringCalculator() 9 | 10 | def test_no_operands(self): 11 | self.assertEqual(self.calc.Add(""), 0) 12 | 13 | def test_n_operands(self): 14 | self.assertEqual(self.calc.Add("1"), 1) 15 | self.assertEqual(self.calc.Add("2"), 2) 16 | self.assertEqual(self.calc.Add("1,2"), 3) 17 | self.assertEqual(self.calc.Add("1,2,3"), 6) 18 | 19 | def test_n_digit_operands(self): 20 | self.assertEqual(self.calc.Add("12,3"), 15) 21 | self.assertEqual(self.calc.Add("21,42"), 63) 22 | self.assertEqual(self.calc.Add("123,12345"), 12468) 23 | 24 | def test_delimiter_newline(self): 25 | self.assertEqual(self.calc.Add("1\n2"), 3) 26 | self.assertEqual(self.calc.Add("1\n2,3"), 6) 27 | 28 | def test_delimiter_choice(self): 29 | self.assertEqual(self.calc.Add("//;\n1;2,3\n4"), 10) 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /string-calculator-mock/kata.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/string-calculator-mock/kata.py -------------------------------------------------------------------------------- /string-calculator-mock/requirements.txt: -------------------------------------------------------------------------------- 1 | mock==1.0.1 2 | wsgiref==0.1.2 3 | -------------------------------------------------------------------------------- /string-calculator-mock/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestKata(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /string-calculator/README.md: -------------------------------------------------------------------------------- 1 | Kata String Calculator 2 | ====================== 3 | 4 | Introducción 5 | ------------ 6 | 7 | Esta kata es una kata de iniciación, en la que se plantea el desarrollo de una calculadora que sumará operandos recibidos en una cadena de carácteres. 8 | 9 | **El desarrollo de la kata está contemplado para practicar el desarrollo guiado por tests e incremental, por lo que es conveniento no saltarse ninguna tarea sin antes haberla completado, ni tampoco leer el contenido de las tareas posteriores.** 10 | 11 | **No es necesario en ningún caso para esta kata comprovar entradas erróneas.** 12 | 13 | Requerimientos 14 | -------------- 15 | 16 | Para la realización de esta kata sólo es necesario disponer de un python 2.7.x o 3.x sin ningún otro paquete adicional. 17 | 18 | Primera fase 19 | ------------ 20 | 21 | El objetivo es conseguir una clase `StringCalculator` que dispondrá de un método `int Add(string)` el cual recibirá una cadena de carácteres con una lista de hasta dos números separados por `,` y devolverá la suma de dichos números. Se debe esperar que la cadena esté vacía, devolviendo la suma 0. 22 | 23 | En esta primera fase es recomendable empezar por el test en el que la cadena está vacía, e ir añadiendo los casos en los que hay uno, y luego dos. 24 | 25 | Segunda fase 26 | ------------ 27 | 28 | En esta fase, el objetivo es que el método `Add` pueda recibir más de un número. 29 | 30 | Tercera fase 31 | ------------ 32 | 33 | La siguiente funcionalidad que se añadirá, consistirá en permitir el salto de línea, `\n`, como separador alternativo. Así, en esta fase: 34 | 35 | * La entrada `"1\n2,3"` se considerará correcto. 36 | * En cambio, la entrada `"1,\n"` se considerará incorrecta. 37 | 38 | Cuarta fase 39 | ----------- 40 | 41 | En esta fase, añadiremos la posibilidad de determinar el separador en la primera línea de la entrada, indicándolo con `"//"`. Por ejemplo, si queremos que el nuevo separador sea `;`, la entrada sería `"//;\n1;2"`. 42 | -------------------------------------------------------------------------------- /string-calculator/kata.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/string-calculator/kata.py -------------------------------------------------------------------------------- /string-calculator/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | from kata import * 5 | 6 | class TestKata(unittest.TestCase): 7 | pass 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /tamagotchi/README.md: -------------------------------------------------------------------------------- 1 | # Tamagotchi Kata 2 | 3 | A Tamagotchi is a small, handheld digital pet that you can feed, play with, put to bed and clean up after. 4 | Look after it well by feeding it the right kinds of foods, showering it with attention and cleaning up after it when required, and your Tamagotchi will grow up to be a smart, well-respected member of society. 5 | 6 | To aid the quick release of this project, we require you to deliver the absolute minimum that could reasonably be called a Tamagotchi pet as soon as possible. 7 | Then we're going to add all of the good stuff- different foods and games to play, all purchasable with our own very special currency, the Kablammo. 8 | 9 | ## Feature 1 10 | 11 | Like we said before, first things first is to get all of the basic needs finished so we have some semblance of a basic Tamagotchi pet. 12 | We're talking about things like hungriness, fullness, tiredness, happiness and of course, the actions required to mitigate these needs. 13 | We're not really sure of the implementation though. 14 | We were thinking of needs on a scale of 1-100, with different activities having different effects on them, but we'll go with whatever you think is best. 15 | 16 | Feeding Tamagotchi 17 | As a Tamagotchi owner 18 | I want to feed my Tamagotchi 19 | So that I can satiate its hunger 20 | * Given I have a Tamagotchi 21 | When I feed it 22 | Then its hungriness is decreased 23 | And its fullness is increased 24 | 25 | Playing with Tamagotchi 26 | As a Tamagotchi owner 27 | I want to play with my Tamagotchi 28 | So that I can make it happier 29 | * Given I have a Tamagotchi 30 | When I play with it 31 | Then its happiness is increased 32 | And its tiredness is increased 33 | 34 | Putting Tamagotchi to bed 35 | As a Tamagotchi owner 36 | I want to put my Tamagotchi to bed 37 | So that I can refill its energy 38 | * Given I have a Tamagotchi 39 | When I put it to bed 40 | Then its tiredness is decreased 41 | 42 | Making Tamagotchi to poop 43 | As a Tamagotchi owner 44 | I want to make my Tamagotchi poop 45 | So that it is more comfortable 46 | * Given I have a Tamagotchi 47 | When I make it poop 48 | Then its fullness is decreased 49 | 50 | Changing Tamagotchi Needs Over Time 51 | As a Tamagotchi owner 52 | I want my Tamagotchi's needs to change over time 53 | So that I have to look after it carefully 54 | * Given I have a Tamagotchi 55 | When time passes 56 | Then it's tiredness is increased 57 | And it's hungriness is increased 58 | And it's happiness is decreased 59 | -------------------------------------------------------------------------------- /tamagotchi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/tamagotchi/__init__.py -------------------------------------------------------------------------------- /tamagotchi/environment.yml: -------------------------------------------------------------------------------- 1 | name: tamagotchi-behave 2 | channels: !!python/tuple 3 | - https://conda.binstar.org/natx 4 | - binstar 5 | - defaults 6 | dependencies: 7 | - openssl=1.0.2j=0 8 | - pip=9.0.1=py35_1 9 | - python=3.5.2=0 10 | - readline=6.2=2 11 | - setuptools=27.2.0=py35_0 12 | - sqlite=3.13.0=0 13 | - tk=8.5.18=0 14 | - wheel=0.29.0=py35_0 15 | - xz=5.2.2=0 16 | - zlib=1.2.8=3 17 | - pip: 18 | - appnope==0.1.0 19 | - behave==1.2.5 20 | - nose==1.3.7 21 | - bleach==1.5.0 22 | - decorator==4.0.10 23 | - entrypoints==0.2.2 24 | - html5lib==0.9999999 25 | - ipykernel==4.5.2 26 | - ipython==5.1.0 27 | - ipython-genutils==0.1.0 28 | - ipywidgets==5.2.2 29 | - jinja2==2.8 30 | - jsonschema==2.5.1 31 | - jupyter==1.0.0 32 | - jupyter-client==4.4.0 33 | - jupyter-console==5.0.0 34 | - jupyter-core==4.2.1 35 | - markupsafe==0.23 36 | - mistune==0.7.3 37 | - nbconvert==5.0.0 38 | - nbformat==4.2.0 39 | - notebook==4.3.0 40 | - pandocfilters==1.4.1 41 | - parse==1.6.6 42 | - parse-type==0.3.4 43 | - pexpect==4.2.1 44 | - pickleshare==0.7.4 45 | - prompt-toolkit==1.0.9 46 | - ptyprocess==0.5.1 47 | - pygments==2.1.3 48 | - pyzmq==16.0.2 49 | - qtconsole==4.2.1 50 | - simplegeneric==0.8.1 51 | - six==1.10.0 52 | - terminado==0.6 53 | - testpath==0.3 54 | - tornado==4.4.2 55 | - traitlets==4.3.1 56 | - wcwidth==0.1.7 57 | - widgetsnbextension==1.2.6 58 | prefix: /Users/ifosch/.miniconda3/envs/tamagotchi-behave 59 | 60 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/tamagotchi/object_oriented/__init__.py -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/bed.feature: -------------------------------------------------------------------------------- 1 | Feature: Putting Tamagotchi to bed 2 | As a Tamagotchi owner 3 | I want to put my Tamagotchi to bed 4 | So that I can refill its energy 5 | Scenario: Simple action 6 | Given I have a Tamagotchi 7 | When I put it to bed 8 | Then its tiredness is decreased -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/environment.py: -------------------------------------------------------------------------------- 1 | def before_all(context): 2 | context.description = "" 3 | 4 | 5 | def before_step(context, step): 6 | context.step = step 7 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/feed.feature: -------------------------------------------------------------------------------- 1 | Feature: Feeding Tamagotchi 2 | As a Tamagotchi owner 3 | I want to feed my Tamagotchi 4 | So that I can satiate its hungriness 5 | Scenario: Simple action 6 | Given I have a Tamagotchi 7 | When I feed it 8 | Then its hungriness is decreased 9 | And its fullness is increased -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/live.feature: -------------------------------------------------------------------------------- 1 | Feature: Changing Tamagotchi Needs Over Time 2 | As a Tamagotchi owner 3 | I want my Tamagotchi's needs to change over time 4 | So that I have to look after it carefully 5 | Scenario: Time passing 6 | Given I have a Tamagotchi 7 | When time passes 8 | Then its tiredness is increased 9 | And its hungriness is increased 10 | And its happiness is decreased -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/play.feature: -------------------------------------------------------------------------------- 1 | Feature: Playing with Tamagotchi 2 | As a Tamagotchi owner 3 | I want to play with my Tamagotchi 4 | So that I can make it happier 5 | Scenario: Simple action 6 | Given I have a Tamagotchi 7 | When I play with it 8 | Then its happiness is increased 9 | And its tiredness is increased -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/poop.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Tamagotchi to poop 2 | As a Tamagotchi owner 3 | I want to make my Tamagotchi poop 4 | So that it is more comfortable 5 | Scenario: Simple action 6 | Given I have a Tamagotchi 7 | When I make it poop 8 | Then its fullness is decreased -------------------------------------------------------------------------------- /tamagotchi/object_oriented/features/steps/basic.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from nose.tools import assert_equal 3 | 4 | from tamagotchi.object_oriented.src.lib.bodyenergy import BodyEnergy 5 | from tamagotchi.object_oriented.src.lib.gameaddiction import GameAddiction 6 | from tamagotchi.object_oriented.src.lib.unitlimitedparameter import \ 7 | UnitLimitedParameter 8 | from tamagotchi.object_oriented.src.lib.unitparameter import UnitParameter 9 | from tamagotchi.object_oriented.src.lib.usualdigestivesystem import \ 10 | UsualDigestiveSystem 11 | from tamagotchi.object_oriented.src.tamagotchi import Tamagotchi 12 | 13 | 14 | # Helpers 15 | 16 | 17 | def update_description(context, prefix): 18 | context.description = '{!s}\n{!s} {!s}'.format( 19 | context.description, 20 | prefix, 21 | context.step.name 22 | ) 23 | 24 | 25 | def update_last_values(context): 26 | context.last_fullness = context.tamagotchi.fullness 27 | context.last_hungriness = context.tamagotchi.hungriness 28 | context.last_happiness = context.tamagotchi.happiness 29 | context.last_tiredness = context.tamagotchi.tiredness 30 | 31 | 32 | # Given 33 | 34 | 35 | @given('I have a Tamagotchi') 36 | def step_having_tamagotchi(context): 37 | update_description(context, 'Given') 38 | 39 | game_param = UnitParameter(5) 40 | digestive_param = UnitLimitedParameter(5, 10) 41 | energy_param = UnitLimitedParameter(5, 10) 42 | 43 | addiction = GameAddiction(happiness=game_param) 44 | digestive_sys = UsualDigestiveSystem(fullness=digestive_param) 45 | energy = BodyEnergy(fullness=energy_param) 46 | 47 | tamagotchi = Tamagotchi( 48 | addiction=addiction, 49 | digestive_sys=digestive_sys, 50 | energy=energy 51 | ) 52 | 53 | context.game_diff = game_param.difference 54 | context.digestive_diff = digestive_param.difference 55 | context.energy_diff = energy_param.difference 56 | context.tamagotchi = tamagotchi 57 | 58 | 59 | # When 60 | 61 | 62 | @when('I feed it') 63 | def step_feed(context): 64 | update_description(context, 'When') 65 | update_last_values(context) 66 | context.tamagotchi.feed_it() 67 | 68 | 69 | @when('I play with it') 70 | def step_play(context): 71 | update_description(context, 'When') 72 | update_last_values(context) 73 | context.tamagotchi.play_with_it() 74 | 75 | 76 | @when('I put it to bed') 77 | def step_put_to_bed(context): 78 | update_description(context, 'When') 79 | update_last_values(context) 80 | context.tamagotchi.put_to_bed() 81 | 82 | 83 | @when('I make it poop') 84 | def step_poop(context): 85 | update_description(context, 'When') 86 | update_last_values(context) 87 | context.tamagotchi.make_it_poop() 88 | 89 | 90 | @when('time passes') 91 | def step_time_passes(context): 92 | update_description(context, 'When') 93 | update_last_values(context) 94 | context.tamagotchi.time_passes() 95 | 96 | 97 | # Then 98 | 99 | 100 | @then('its {parameter} is {change}') 101 | def step_parameter_change(context, parameter, change): 102 | update_description(context, 'Then') 103 | 104 | if change == 'increased': 105 | diff_sign = 1 106 | elif change == 'decreased': 107 | diff_sign = -1 108 | else: 109 | raise ValueError('Change "{!s}" is not implemented'.format(change)) 110 | 111 | if parameter == 'fullness': 112 | assert_equal( 113 | context.tamagotchi.fullness, 114 | context.last_fullness + context.digestive_diff * diff_sign, 115 | context.description 116 | ) 117 | elif parameter == 'hungriness': 118 | assert_equal( 119 | context.tamagotchi.hungriness, 120 | context.last_hungriness + context.digestive_diff * diff_sign, 121 | context.description 122 | ) 123 | elif parameter == 'happiness': 124 | assert_equal( 125 | context.tamagotchi.happiness, 126 | context.last_happiness + context.game_diff * diff_sign, 127 | context.description 128 | ) 129 | elif parameter == 'tiredness': 130 | assert_equal( 131 | context.tamagotchi.tiredness, 132 | context.last_tiredness + context.energy_diff * diff_sign, 133 | context.description 134 | ) 135 | else: 136 | raise ValueError('Parameter "{!s}" is not implemented'.format(parameter)) 137 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/tamagotchi/object_oriented/src/__init__.py -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/tamagotchi/object_oriented/src/interface/__init__.py -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/addiction.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | 4 | class Addiction(metaclass=ABCMeta): 5 | 6 | @abstractmethod 7 | def satisfy(self): 8 | pass 9 | 10 | @abstractmethod 11 | def dissatisfy(self): 12 | pass 13 | 14 | @abstractproperty 15 | def happiness(self): 16 | pass 17 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/digestivesystem.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | 4 | class DigestiveSystem(metaclass=ABCMeta): 5 | 6 | @abstractmethod 7 | def eat(self): 8 | pass 9 | 10 | @abstractmethod 11 | def poo(self): 12 | pass 13 | 14 | @abstractmethod 15 | def burn(self): 16 | pass 17 | 18 | @abstractproperty 19 | def hungriness(self): 20 | pass 21 | 22 | @abstractproperty 23 | def fullness(self): 24 | pass 25 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/energy.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | 4 | class Energy(metaclass=ABCMeta): 5 | 6 | @abstractproperty 7 | def fullness(self): 8 | pass 9 | 10 | @abstractproperty 11 | def emptiness(self): 12 | pass 13 | 14 | @abstractmethod 15 | def charge(self): 16 | pass 17 | 18 | @abstractmethod 19 | def discharge(self): 20 | pass 21 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/limitedparameter.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractproperty 2 | 3 | from tamagotchi.object_oriented.src.interface.parameter import Parameter 4 | 5 | 6 | class LimitedParameter(Parameter, metaclass=ABCMeta): 7 | 8 | @abstractproperty 9 | def remain(self): 10 | pass 11 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/interface/parameter.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, abstractproperty 2 | 3 | 4 | class Parameter(metaclass=ABCMeta): 5 | 6 | @abstractmethod 7 | def increase(self): 8 | pass 9 | 10 | @abstractmethod 11 | def decrease(self): 12 | pass 13 | 14 | @abstractproperty 15 | def value(self): 16 | pass 17 | 18 | @abstractproperty 19 | def difference(self): 20 | pass 21 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/tamagotchi/object_oriented/src/lib/__init__.py -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/bodyenergy.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.energy import Energy 2 | from tamagotchi.object_oriented.src.interface.limitedparameter import \ 3 | LimitedParameter 4 | 5 | 6 | class BodyEnergy(Energy): 7 | 8 | def __init__(self, fullness: LimitedParameter): 9 | self._fullness = fullness 10 | 11 | @property 12 | def fullness(self): 13 | return self._fullness.value 14 | 15 | @property 16 | def emptiness(self): 17 | return self._fullness.remain 18 | 19 | def discharge(self): 20 | self._fullness.decrease() 21 | 22 | def charge(self): 23 | self._fullness.increase() 24 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/gameaddiction.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.addiction import Addiction 2 | from tamagotchi.object_oriented.src.interface.parameter import Parameter 3 | 4 | 5 | class GameAddiction(Addiction): 6 | 7 | def __init__(self, happiness: Parameter): 8 | self._happiness = happiness 9 | 10 | @property 11 | def happiness(self): 12 | return self._happiness.value 13 | 14 | def dissatisfy(self): 15 | self._happiness.decrease() 16 | 17 | def satisfy(self): 18 | self._happiness.increase() 19 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/unitlimitedparameter.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.limitedparameter import \ 2 | LimitedParameter 3 | 4 | 5 | class UnitLimitedParameter(LimitedParameter): 6 | 7 | _UNIT_VALUE = 1 8 | 9 | def __init__(self, start_value=0, limit=0): 10 | self._value = start_value 11 | self._limit = limit 12 | 13 | @property 14 | def value(self): 15 | return self._value 16 | 17 | @property 18 | def difference(self): 19 | return self._UNIT_VALUE 20 | 21 | @property 22 | def remain(self): 23 | if self._limit: 24 | return self._limit - self._value 25 | return None 26 | 27 | def decrease(self): 28 | self._value -= self._UNIT_VALUE 29 | 30 | def increase(self): 31 | if not self._limit or self._value + self._UNIT_VALUE <= self._limit: 32 | self._value += self._UNIT_VALUE 33 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/unitparameter.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.parameter import Parameter 2 | 3 | 4 | class UnitParameter(Parameter): 5 | 6 | _UNIT_VALUE = 1 7 | 8 | def __init__(self, start_value=0): 9 | self._value = start_value 10 | 11 | @property 12 | def value(self): 13 | return self._value 14 | 15 | @property 16 | def difference(self): 17 | return self._UNIT_VALUE 18 | 19 | def decrease(self): 20 | self._value -= self._UNIT_VALUE 21 | 22 | def increase(self): 23 | self._value += self._UNIT_VALUE 24 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/lib/usualdigestivesystem.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.digestivesystem import \ 2 | DigestiveSystem 3 | from tamagotchi.object_oriented.src.interface.limitedparameter import \ 4 | LimitedParameter 5 | 6 | 7 | class UsualDigestiveSystem(DigestiveSystem): 8 | 9 | def __init__(self, fullness: LimitedParameter): 10 | self._fullness = fullness 11 | 12 | @property 13 | def hungriness(self): 14 | return self._fullness.remain 15 | 16 | @property 17 | def fullness(self): 18 | return self._fullness.value 19 | 20 | def eat(self): 21 | self._fullness.increase() 22 | 23 | def poo(self): 24 | self._fullness.decrease() 25 | 26 | def burn(self): 27 | self._fullness.decrease() 28 | -------------------------------------------------------------------------------- /tamagotchi/object_oriented/src/tamagotchi.py: -------------------------------------------------------------------------------- 1 | from tamagotchi.object_oriented.src.interface.addiction import Addiction 2 | from tamagotchi.object_oriented.src.interface.digestivesystem import \ 3 | DigestiveSystem 4 | from tamagotchi.object_oriented.src.interface.energy import Energy 5 | 6 | 7 | class Tamagotchi(object): 8 | 9 | def __init__( 10 | self, 11 | addiction: Addiction, 12 | digestive_sys: DigestiveSystem, 13 | energy: Energy 14 | ): 15 | self._addiction = addiction 16 | self._digestive_sys = digestive_sys 17 | self._energy = energy 18 | 19 | @property 20 | def fullness(self): 21 | return self._digestive_sys.fullness 22 | 23 | @property 24 | def hungriness(self): 25 | return self._digestive_sys.hungriness 26 | 27 | @property 28 | def happiness(self): 29 | return self._addiction.happiness 30 | 31 | @property 32 | def tiredness(self): 33 | return self._energy.emptiness 34 | 35 | def feed_it(self): 36 | self._digestive_sys.eat() 37 | 38 | def play_with_it(self): 39 | self._addiction.satisfy() 40 | self._energy.discharge() 41 | 42 | def put_to_bed(self): 43 | self._energy.charge() 44 | 45 | def make_it_poop(self): 46 | self._digestive_sys.poo() 47 | 48 | def time_passes(self): 49 | self._energy.discharge() 50 | self._digestive_sys.burn() 51 | self._addiction.dissatisfy() 52 | -------------------------------------------------------------------------------- /tamagotchi/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | behave==1.2.5 3 | nose==1.3.7 4 | bleach==1.5.0 5 | decorator==4.0.10 6 | entrypoints==0.2.2 7 | html5lib==0.9999999 8 | ipykernel==4.5.2 9 | ipython==5.1.0 10 | ipython-genutils==0.1.0 11 | ipywidgets==5.2.2 12 | Jinja2==2.8 13 | jsonschema==2.5.1 14 | jupyter==1.0.0 15 | jupyter-client==4.4.0 16 | jupyter-console==5.0.0 17 | jupyter-core==4.2.1 18 | MarkupSafe==0.23 19 | mistune==0.7.3 20 | nbconvert==5.0.0 21 | nbformat==4.2.0 22 | notebook>=5.7.2 23 | pandocfilters==1.4.1 24 | parse==1.6.6 25 | parse-type==0.3.4 26 | pexpect==4.2.1 27 | pickleshare==0.7.4 28 | prompt-toolkit==1.0.9 29 | ptyprocess==0.5.1 30 | Pygments==2.1.3 31 | pyzmq==16.0.2 32 | qtconsole==4.2.1 33 | simplegeneric==0.8.1 34 | six==1.10.0 35 | terminado==0.6 36 | testpath==0.3 37 | tornado==4.4.2 38 | traitlets==4.3.1 39 | wcwidth==0.1.7 40 | widgetsnbextension==1.2.6 41 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | Hola 2 | -------------------------------------------------------------------------------- /understanding-sockets/README.md: -------------------------------------------------------------------------------- 1 | Understanding sockets 2 | ===================== 3 | 4 | A test driven session introducing the socket communication with Python. 5 | 6 | After a brief talk on [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets), we will do some exercises with unix and 7 | internet socket using the Python interface module. 8 | 9 | The goal of this session is to achieve solid knowledge about what is a socket 10 | and how to implement both client and server sides with Python. 11 | 12 | Please, ensure you have any Python interpreter (preferably 3.5) and pytest 13 | installed in your preferred environment. 14 | 15 | ## Slides ## 16 | 17 | https://docs.google.com/presentation/d/1TOtIxjFnkAXRchshL_UzhMY8dxWyK2-LEKkCS9nY7U8/edit#slide=id.p 18 | 19 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/understanding-sockets/hellosocket/__init__.py -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/berkeleysocketclient.py: -------------------------------------------------------------------------------- 1 | from hellosocket.contract.berkeleyside import BerkeleySide 2 | from hellosocket.contract.client import Client 3 | 4 | 5 | class BerkeleySocketClient(Client, BerkeleySide): 6 | 7 | def __init__(self, **kwargs): 8 | self._address = self.parse_address(kwargs) 9 | unix_type = type(self._address) == str 10 | self._socket = self.build_socket(unix_type) 11 | 12 | def disconnect(self): 13 | print("[client] Close socket") 14 | self._socket.close() 15 | 16 | def read(self): 17 | print("[client] Reads buffer") 18 | received = self._socket.recv(1024).decode() 19 | return received 20 | 21 | def write(self, message): 22 | print("[client] Sends message {!s}".format(message)) 23 | self._socket.sendall(message.encode()) 24 | 25 | def connect(self): 26 | self._socket.connect(self._address) 27 | 28 | 29 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/berkeleysocketserver.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | 3 | from hellosocket.contract.berkeleyside import BerkeleySide 4 | from hellosocket.contract.server import Server 5 | 6 | 7 | class BerkeleySocketServer(Server, BerkeleySide): 8 | 9 | def __init__(self, **kwargs): 10 | self._address = self.parse_address(kwargs) 11 | unix_type = type(self._address) == str 12 | self._socket = self.build_socket(unix_type) 13 | self._listening = False 14 | self._async_listener = Thread(target=self._listen_task) 15 | 16 | def _listen_task(self): 17 | self._socket.bind(self._address) 18 | self._socket.listen(1) 19 | client_conn, address = self._socket.accept() 20 | with client_conn: 21 | print("[server] Connects by {!s}".format(address)) 22 | self._listening = True 23 | while self._listening: 24 | received = client_conn.recv(1024).decode() 25 | answer = self.HELLO_FORMAT.format(received) 26 | self._send_all(client_conn, address, answer) 27 | 28 | def _send_all(self, conn, address, answer): 29 | if self._listening: 30 | try: 31 | conn.sendall(answer.encode()) 32 | except BrokenPipeError: 33 | print("[server] Broken connection with {!s}".format(address)) 34 | raise 35 | 36 | def listen(self): 37 | self._async_listener.start() 38 | 39 | def close(self): 40 | print("[server] Shutdowns socket") 41 | self._listening = False 42 | self._socket.shutdown(self.SHUTDOWN_FLAG) 43 | self._socket.close() 44 | 45 | if self._async_listener.is_alive(): 46 | print("[server] Joins background task") 47 | self._async_listener.join() 48 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/contract/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/understanding-sockets/hellosocket/contract/__init__.py -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/contract/berkeleyside.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket 3 | 4 | 5 | class BerkeleySide: 6 | 7 | SHUTDOWN_FLAG = socket.SHUT_RDWR 8 | 9 | @classmethod 10 | def parse_address(cls, params): 11 | if "host" in params and "port" in params: 12 | address = (params["host"], params["port"]) 13 | elif "file_path" in params: 14 | address = params["file_path"] 15 | if os.path.exists(address): 16 | os.remove(address) 17 | else: 18 | raise AttributeError("Missing address") 19 | 20 | return address 21 | 22 | @classmethod 23 | def build_socket(cls, unix_type=False): 24 | if unix_type: 25 | socket_af = socket.AF_UNIX 26 | else: 27 | socket_af = socket.AF_INET 28 | return socket.socket(socket_af, socket.SOCK_STREAM) 29 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/contract/client.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class Client(metaclass=ABCMeta): 5 | 6 | HELLO = "Hello, my name is client" 7 | 8 | @abstractmethod 9 | def connect(self): 10 | pass 11 | 12 | @abstractmethod 13 | def write(self, message): 14 | pass 15 | 16 | @abstractmethod 17 | def read(self): 18 | pass 19 | 20 | @abstractmethod 21 | def disconnect(self): 22 | pass 23 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/contract/pseudoside.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import time 4 | 5 | 6 | class PseudoSide: 7 | 8 | @classmethod 9 | def append_to_file(cls, file_path, content, caller): 10 | print("[{!s}] append".format(caller)) 11 | with open(file_path, "a") as pseudo_socket: 12 | pseudo_socket.write(content) 13 | time.sleep(0.5) # Let it read for other 14 | 15 | @classmethod 16 | def read_and_remove(cls, file_path, caller): 17 | print("[{!s}] tries to read".format(caller)) 18 | with open(file_path, "r") as pseudo_socket: 19 | received = pseudo_socket.read() 20 | if not received: 21 | return 22 | os.remove(file_path) 23 | print("[{!s}] read and remove ({!s})".format(caller, received)) 24 | return received 25 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/contract/server.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class Server(metaclass=ABCMeta): 5 | 6 | HELLO_FORMAT = "Received message: {!s}" 7 | 8 | @abstractmethod 9 | def listen(self): 10 | pass 11 | 12 | @abstractmethod 13 | def close(self): 14 | pass 15 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/multiconnsyncberkeleysocketserver.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | from threading import Thread 3 | 4 | from hellosocket.berkeleysocketserver import BerkeleySocketServer 5 | 6 | 7 | class MultiConnSyncBerkeleySocketServer(BerkeleySocketServer): 8 | 9 | PREFIX = ":) " 10 | 11 | def __init__(self, **kwargs): 12 | super().__init__(**kwargs) 13 | self._listeners = Queue() 14 | 15 | def _build_async_listener(self, client_conn, address): 16 | return Thread( 17 | target=self._listen_task_by_conn, 18 | args=(client_conn, address) 19 | ) 20 | 21 | def listen(self): 22 | self._socket.bind(self._address) 23 | self._socket.listen(1) 24 | self._listening = True 25 | while self._listening: 26 | client_conn, address = self._socket.accept() 27 | listener = self._build_async_listener(client_conn, address) 28 | listener.start() 29 | self._listeners.put((address, listener)) 30 | 31 | def _listen_task_by_conn(self, client_conn, address): 32 | with client_conn: 33 | print("[server] Connects by {!s}".format(address)) 34 | self._send_all( 35 | client_conn, 36 | address, 37 | "PyDojo PyBCN Socket Server v-0.1b\n" 38 | ) 39 | self._send_all(client_conn, address, "Type 'quit' to exit\n") 40 | self._send_all(client_conn, address, self.PREFIX) 41 | while self._listening: 42 | try: 43 | self._answer(client_conn, address) 44 | except BrokenPipeError: 45 | break 46 | 47 | def _answer(self, client_conn, address): 48 | received = client_conn.recv(1024).decode() 49 | if received[:-1] == 'quit': 50 | print( 51 | "[server] {!s} exited".format(address) 52 | ) 53 | raise BrokenPipeError('Gracefully disconnected') 54 | print( 55 | "[server] Received {!s} from {!s}".format( 56 | received[:-1], 57 | address 58 | ) 59 | ) 60 | answer = self.HELLO_FORMAT.format(received) 61 | self._send_all(client_conn, address, answer) 62 | self._send_all(client_conn, address, self.PREFIX) 63 | 64 | 65 | if __name__ == "__main__": 66 | bind_address = "0.0.0.0" 67 | port = 43210 68 | server = MultiConnSyncBerkeleySocketServer(host=bind_address, port=port) 69 | server.listen() 70 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/pseudosocketclient.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from hellosocket.contract.client import Client 4 | from hellosocket.contract.pseudoside import PseudoSide 5 | 6 | 7 | class PseudoSocketClient(Client, PseudoSide): 8 | 9 | def __init__(self, file_path): 10 | self._file_path = file_path 11 | 12 | def read(self): 13 | print("[client] Reads buffer") 14 | return self.read_and_remove(self._file_path, "client") 15 | 16 | def write(self, message): 17 | print("[client] Sends message {!s}".format(message)) 18 | self.append_to_file(self._file_path, message, "client") 19 | 20 | def disconnect(self): 21 | print("[client] Close socket") 22 | 23 | def connect(self): 24 | print("[client] Open socket") 25 | os.path.isfile(self._file_path) 26 | -------------------------------------------------------------------------------- /understanding-sockets/hellosocket/pseudosocketserver.py: -------------------------------------------------------------------------------- 1 | import os 2 | from threading import Thread 3 | 4 | import time 5 | 6 | from hellosocket.contract.pseudoside import PseudoSide 7 | from hellosocket.contract.server import Server 8 | 9 | 10 | class PseudoSocketServer(Server, PseudoSide): 11 | 12 | def __init__(self, file_path): 13 | if os.path.exists(file_path): 14 | os.remove(file_path) 15 | self._file_path = file_path 16 | self._listening = False 17 | self._async_listener = Thread(target=self._listen_task) 18 | 19 | def _listen_task(self): 20 | print("[server] Connects by {!s}".format(self._file_path)) 21 | open(self._file_path, 'a').close() 22 | self._listening = True 23 | while self._listening: 24 | received = self.read_and_remove(self._file_path, "server") 25 | if received: 26 | print("[server] Server got message") 27 | answer = self.HELLO_FORMAT.format(received) 28 | self.append_to_file(self._file_path, answer, "server") 29 | time.sleep(0.25) 30 | 31 | def listen(self): 32 | self._async_listener.start() 33 | 34 | def close(self): 35 | print("[server] Shutdowns socket") 36 | self._listening = False 37 | 38 | if self._async_listener.is_alive(): 39 | print("[server] Joins background task") 40 | self._async_listener.join() 41 | -------------------------------------------------------------------------------- /understanding-sockets/requirements.txt: -------------------------------------------------------------------------------- 1 | py==1.4.32 2 | pytest==3.0.5 3 | -------------------------------------------------------------------------------- /understanding-sockets/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/understanding-sockets/tests/__init__.py -------------------------------------------------------------------------------- /understanding-sockets/tests/hello.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hello workflow: 3 | 4 | 1. Server starts listening. 5 | 2. Client connects to server. 6 | 3. Client sends a hello message to server. 7 | 4. Server reads the message and returns the ack for the received message. 8 | 5. Client closes connection. 9 | 5. Server closes socket. 10 | 6. Assertions. 11 | """ 12 | import sys 13 | 14 | import time 15 | 16 | sys.path.append("..") 17 | 18 | from hellosocket.contract.client import Client 19 | from hellosocket.contract.server import Server 20 | 21 | EXPECTED_HELLO_CLIENT = Client.HELLO 22 | EXPECTED_HELLO_SERVER_FORMAT = Server.HELLO_FORMAT 23 | 24 | 25 | def expected_server_response(): 26 | return EXPECTED_HELLO_SERVER_FORMAT.format(EXPECTED_HELLO_CLIENT) 27 | 28 | 29 | def say_hello(client: Client, server: Server): 30 | """ 31 | Assert received ack from the server in client side when server listens in 32 | a separated thread. 33 | :param client: Client 34 | :param server: Server 35 | :return: None 36 | """ 37 | server.listen() 38 | time.sleep(1) # Let start socket server 39 | client.connect() 40 | client.write(EXPECTED_HELLO_CLIENT) 41 | answer = client.read() 42 | client.disconnect() 43 | server.close() 44 | print("[DEBUG]") 45 | print(answer) 46 | print(expected_server_response()) 47 | assert answer == expected_server_response() 48 | -------------------------------------------------------------------------------- /understanding-sockets/tests/test_berkeley.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("..") 3 | 4 | from tests import hello 5 | 6 | from hellosocket.berkeleysocketserver import BerkeleySocketServer 7 | from hellosocket.berkeleysocketclient import BerkeleySocketClient 8 | 9 | 10 | def test_unix_pair(): 11 | file_path = "/tmp/unix_socket.sock" 12 | client = BerkeleySocketClient(file_path=file_path) 13 | server = BerkeleySocketServer(file_path=file_path) 14 | hello.say_hello(client, server) 15 | 16 | 17 | def test_internet_pair(): 18 | host = "127.0.0.1" 19 | bind_address = "127.0.0.1" 20 | port = 43210 21 | client = BerkeleySocketClient(host=host, port=port) 22 | server = BerkeleySocketServer(host=bind_address, port=port) 23 | hello.say_hello(client, server) 24 | -------------------------------------------------------------------------------- /understanding-sockets/tests/test_pseudo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("..") 3 | 4 | from tests import hello 5 | 6 | from hellosocket.pseudosocketclient import PseudoSocketClient 7 | from hellosocket.pseudosocketserver import PseudoSocketServer 8 | 9 | 10 | def test_pseudo_pair(): 11 | file_path = "/tmp/pseudo_socket.tmp" 12 | client = PseudoSocketClient(file_path) 13 | server = PseudoSocketServer(file_path) 14 | 15 | hello.say_hello(client, server) 16 | -------------------------------------------------------------------------------- /webpy/README.md: -------------------------------------------------------------------------------- 1 | Getting started with web.py framework 2 | ===================================== 3 | 4 | ![webpy Logo](http://webpy.org/static/webpy.gif) 5 | 6 | This session is based on several introduction proves of concept using web.py 7 | lightweight framework. 8 | 9 | There are lots of python web frameworks, although web.py has some features and 10 | history that make it special: 11 | 12 | * Started by [Aaron Swartz](http://www.aaronsw.com/), just a very important 13 | person. 14 | * [reddit.com](http://reddit.com/) used it as it grew to become one of the top 15 | 1000 sites and served millions of daily page views. 16 | * It is a good way to understand what is happening during a request/response 17 | HTTP transaction. 18 | 19 | Expect this session to be more practice than theory. 20 | 21 | More information about web.py in the official website 22 | [http://webpy.org/](http://webpy.org/). 23 | 24 | **web.py is native for python 2.7+ and python 3 is not supported.** 25 | 26 | ## Session key points ## 27 | 28 | 1. Installing dependencies. 29 | 2. Hello, world! 30 | 3. Temporary remote access by ngrok. 31 | 4. Templates. 32 | * Fail with indentation. 33 | * Update python file and template and check with no server restarting. 34 | * Template not found due to current working directory. 35 | 5. Forms. 36 | * Printing Form. Escaped and unescaped passed html. 37 | * Submit Form and get body. 38 | * Fields validation. 39 | 6. Basic Authentication. 40 | * Http authentication header. 41 | * Logout by Unauthorized status. -------------------------------------------------------------------------------- /webpy/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BCNDojos/pyDojos/33712a8e23803ec111eb0b038a58993ae92d91e8/webpy/src/__init__.py -------------------------------------------------------------------------------- /webpy/src/endpoint.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import re 3 | 4 | import web 5 | import os 6 | 7 | from web import form 8 | 9 | CWD = os.path.basename(os.path.dirname(os.path.realpath(__file__))) 10 | 11 | render = web.template.render('{!s}/templates/'.format(CWD)) 12 | 13 | urls = ( 14 | '/', 'Index', 15 | '/login', 'Login', 16 | '/private/(.*)', 'Private', 17 | ) 18 | 19 | 20 | class ForcedUnauthorized(web.Unauthorized): 21 | """`401 Unauthorized` error.""" 22 | message = "unauthorized" 23 | 24 | def __init__(self, headers, message=None): 25 | status = "401 Unauthorized" 26 | headers.update({'Content-Type': 'text/html'}) 27 | web.HTTPError.__init__(self, status, headers, message or self.message) 28 | 29 | 30 | class Private: 31 | 32 | def __init__(self): 33 | self._allowed = [ 34 | ('admin', '12345678'), 35 | ('test', '87654321') 36 | ] 37 | 38 | def GET(self, rest): 39 | if rest == 'logout': 40 | raise ForcedUnauthorized( 41 | { 42 | 'WWW-Authenticate': 'Basic realm="Auth example"' 43 | } 44 | ) 45 | auth = web.ctx.env.get('HTTP_AUTHORIZATION') 46 | if auth is None: 47 | auth_is_required = True 48 | else: 49 | auth = re.sub('^Basic ', '', auth) 50 | username, password = base64.decodestring(auth).split(':') 51 | if (username, password) in self._allowed: 52 | return render.private(username) 53 | else: 54 | auth_is_required = True 55 | if auth_is_required: 56 | web.header('WWW-Authenticate', 'Basic realm="Auth example"') 57 | web.ctx.status = '401 Unauthorized' 58 | return render.http401() 59 | 60 | 61 | class Login: 62 | 63 | def __init__(self): 64 | self._login_form = form.Form( 65 | form.Textbox('username', form.notnull), 66 | form.Password( 67 | 'password', 68 | form.notnull, 69 | form.regexp('\d+', 'Digits only'), 70 | form.Validator( 71 | 'Must be larger or equal than 8', 72 | lambda x: len(x) >= 8 73 | ) 74 | ) 75 | ) 76 | 77 | def GET(self): 78 | my_form = self._login_form() 79 | return render.login(my_form) 80 | 81 | def POST(self): 82 | my_form = self._login_form() 83 | if not my_form.validates(): 84 | return render.login(my_form) 85 | else: 86 | result = 'my_form.d.username and my_form.["username"].value are ' \ 87 | 'equivalent ways of extracting the validated arguments ' \ 88 | 'from the form.\n\n' 89 | result += 'Got! username: "{!s}", password: "{!s}"'.format( 90 | my_form['username'].value, my_form['password'].value 91 | ) 92 | return result 93 | 94 | 95 | class Index: 96 | 97 | def GET(self): 98 | fullname = 'people' 99 | title = 'This template Title' 100 | items = [ 101 | "First", 102 | "Second", 103 | "Third" 104 | ] 105 | 106 | return render.index(fullname, title, items) 107 | 108 | if __name__ == "__main__": 109 | app = web.application(urls, globals()) 110 | app.run() 111 | -------------------------------------------------------------------------------- /webpy/src/templates/http401.html: -------------------------------------------------------------------------------- 1 | $def with () 2 | 3 | 4 | 5 | 6 | Unauthorized 7 | 8 | 9 | 10 | Go back 11 | 12 |

You are not authorized.

13 | 14 | -------------------------------------------------------------------------------- /webpy/src/templates/index.html: -------------------------------------------------------------------------------- 1 | $def with (fullname, title, items) 2 | 3 | 4 | 5 | 6 | 7 | $if title: 8 | $title 9 | $else: 10 | This is my page 11 | 12 | 13 | 14 |

Hello, $fullname!

15 | 16 | Go to Login 17 | 18 |

More about

19 | 20 |
More information about templates here 21 | http://webpy.org/docs/0.3/templetor 22 |
    23 | $while items: 24 |
  • $items.pop()
  • 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /webpy/src/templates/login.html: -------------------------------------------------------------------------------- 1 | $def with (my_form) 2 | 3 | 4 | 5 | 6 | Login page 7 | 8 | 9 | 10 | Index / Login 11 | 12 |

Login Form

13 | 14 |
15 | 16 | $if not my_form.valid:

Errors found, try again:

17 | $:my_form.render() 18 | 19 | 20 |
21 |
22 |
More information about form module here 23 | http://webpy.org/form 24 |
25 | 26 | -------------------------------------------------------------------------------- /webpy/src/templates/private.html: -------------------------------------------------------------------------------- 1 | $def with (username) 2 | 3 | 4 | 5 | 6 | Private page 7 | 8 | 9 | 10 | Index / Private 11 | 12 |

Hello $username

13 | 14 |

This page is only for you.

15 | 16 |

Logout

17 | 18 | --------------------------------------------------------------------------------