├── Chapter05 ├── tests │ ├── __init__.py │ └── test_dependencies │ │ ├── __init__.py │ │ └── test_redis.py ├── temp_messenger │ ├── __init__.py │ ├── dependencies │ │ ├── jinja2.py │ │ └── redis.py │ ├── templates │ │ └── home.html │ └── service.py ├── requirements │ ├── test.in │ ├── base.in │ ├── test.txt │ └── base.txt ├── config.yaml └── readme.md ├── Chapter06 ├── tests │ ├── __init__.py │ ├── test_dependencies │ │ ├── __init__.py │ │ └── test_messages.py │ └── conftest.py ├── temp_messenger │ ├── __init__.py │ ├── templates │ │ ├── login.html │ │ ├── sign_up.html │ │ └── home.html │ ├── user_service.py │ ├── dependencies │ │ ├── jinja2.py │ │ ├── messages.py │ │ └── users.py │ └── message_service.py ├── requirements │ ├── test.in │ ├── base.in │ ├── test.txt │ └── base.txt ├── readme.md ├── setup_db.py └── config.yaml ├── Chapter07 └── gamestore │ ├── main │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_auto_20180215_2147.py │ │ ├── 0004_auto_20180215_2156.py │ │ ├── 0002_pricelist.py │ │ ├── 0005_shoppingcart_shoppingcartitem.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── templates │ │ └── main │ │ │ ├── all_games.html │ │ │ ├── highlighted.html │ │ │ ├── signup.html │ │ │ ├── create_account_success.html │ │ │ ├── index.html │ │ │ ├── games-list.html │ │ │ └── cart.html │ ├── urls.py │ ├── forms.py │ └── models.py │ ├── gamestore │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py │ ├── static │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ └── images │ │ └── placeholder.png │ ├── package.json │ ├── Pipfile │ ├── package-lock.json │ ├── manage.py │ └── templates │ ├── _loginpartial.html │ ├── login.html │ └── base.html ├── Chapter08 ├── gamestore │ ├── main │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_auto_20180215_2147.py │ │ │ ├── 0004_auto_20180215_2156.py │ │ │ ├── 0002_pricelist.py │ │ │ ├── 0005_shoppingcart_shoppingcartitem.py │ │ │ └── 0001_initial.py │ │ ├── tests.py │ │ ├── apps.py │ │ ├── admin.py │ │ ├── templates │ │ │ └── main │ │ │ │ ├── all_games.html │ │ │ │ ├── highlighted.html │ │ │ │ ├── signup.html │ │ │ │ ├── create_account_success.html │ │ │ │ ├── index.html │ │ │ │ ├── my-orders.html │ │ │ │ ├── games-list.html │ │ │ │ └── cart.html │ │ ├── urls.py │ │ └── forms.py │ ├── gamestore │ │ ├── __init__.py │ │ ├── wsgi.py │ │ └── urls.py │ ├── static │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ └── images │ │ │ └── placeholder.png │ ├── package.json │ ├── Pipfile │ ├── package-lock.json │ ├── manage.py │ └── templates │ │ ├── _loginpartial.html │ │ ├── login.html │ │ └── base.html └── microservices │ ├── order │ ├── main │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ └── 0001_initial.py │ │ ├── apps.py │ │ ├── status.py │ │ ├── admin.py │ │ ├── exceptions.py │ │ ├── urls.py │ │ ├── view_helper.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── views.py │ │ └── managers.py │ ├── order │ │ ├── __init__.py │ │ ├── wsgi.py │ │ ├── urls.py │ │ └── settings.py │ ├── manage.py │ └── send_order.py │ └── Pipfile ├── Chapter01 ├── weatherterm │ ├── parsers │ │ └── __init__.py │ ├── core │ │ ├── base_enum.py │ │ ├── unit.py │ │ ├── forecast_type.py │ │ ├── set_unit_action.py │ │ ├── __init__.py │ │ ├── request.py │ │ ├── mapper.py │ │ ├── parser_loader.py │ │ ├── unit_converter.py │ │ └── forecast.py │ └── __main__.py └── requirements.txt ├── Chapter09 ├── microservices │ ├── order │ │ ├── main │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ └── 0001_initial.py │ │ │ ├── apps.py │ │ │ ├── notification_type.py │ │ │ ├── status.py │ │ │ ├── notifier.py │ │ │ ├── exceptions.py │ │ │ ├── admin.py │ │ │ ├── urls.py │ │ │ ├── view_helper.py │ │ │ ├── models.py │ │ │ ├── serializers.py │ │ │ ├── managers.py │ │ │ └── views.py │ │ ├── order │ │ │ ├── __init__.py │ │ │ ├── wsgi.py │ │ │ └── urls.py │ │ ├── manage.py │ │ └── send_order.py │ └── Pipfile └── notifier │ ├── templates │ ├── order_shipped_template.html │ └── order_received_template.html │ └── Pipfile ├── Chapter04 ├── currency_converter │ ├── config │ │ ├── __init__.py │ │ ├── config_error.py │ │ └── config.py │ ├── core │ │ ├── __init__.py │ │ ├── request.py │ │ ├── currency.py │ │ ├── db.py │ │ ├── actions.py │ │ └── cmdline_parser.py │ └── __main__.py └── Pipfile ├── Chapter02 └── musicterminal │ ├── pytify │ ├── core │ │ ├── exceptions.py │ │ ├── request_type.py │ │ ├── search_type.py │ │ ├── __init__.py │ │ ├── album.py │ │ ├── artist.py │ │ ├── player.py │ │ ├── parameter.py │ │ ├── search.py │ │ ├── request.py │ │ └── config.py │ └── auth │ │ ├── auth_method.py │ │ ├── __init__.py │ │ ├── authorization.py │ │ └── auth.py │ ├── requirements.txt │ ├── client │ ├── empty_results_error.py │ ├── __init__.py │ ├── alignment.py │ ├── menu_item.py │ ├── panel.py │ ├── data_manager.py │ └── menu.py │ ├── templates │ └── index.html │ ├── config.yaml │ ├── spotify_auth.py │ └── app.py ├── Chapter03 ├── core │ ├── twitter │ │ ├── __init__.py │ │ ├── hashtag.py │ │ └── hashtagstats_manager.py │ ├── models │ │ ├── __init__.py │ │ └── models.py │ ├── __init__.py │ ├── app_logger.py │ ├── runner.py │ ├── cmdline_parser.py │ ├── config.py │ └── request.py ├── templates │ └── index.html ├── Pipfile ├── config.yaml ├── logconfig.ini ├── twitter_auth.py └── app.py ├── LICENSE ├── .gitignore └── README.md /Chapter05/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/gamestore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/gamestore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/tests/test_dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/tests/test_dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/requirements/test.in: -------------------------------------------------------------------------------- 1 | pytest 2 | fakeredis 3 | -------------------------------------------------------------------------------- /Chapter06/requirements/test.in: -------------------------------------------------------------------------------- 1 | pytest 2 | fakeredis 3 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/requirements/base.in: -------------------------------------------------------------------------------- 1 | nameko 2 | redis 3 | jinja2 4 | -------------------------------------------------------------------------------- /Chapter01/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.6.0 2 | selenium==3.6.0 3 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import get_config -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/exceptions.py: -------------------------------------------------------------------------------- 1 | class BadRequestError(Exception): 2 | pass -------------------------------------------------------------------------------- /Chapter02/musicterminal/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.18.1 2 | Flask==0.12.2 3 | PyYAML==3.12 4 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/config_error.py: -------------------------------------------------------------------------------- 1 | class ConfigError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/empty_results_error.py: -------------------------------------------------------------------------------- 1 | class EmptyResultsError(Exception): 2 | pass -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .menu import Menu 2 | from .data_manager import DataManager -------------------------------------------------------------------------------- /Chapter07/gamestore/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /Chapter03/core/twitter/__init__.py: -------------------------------------------------------------------------------- 1 | from .hashtag import Hashtag 2 | from .hashtagstats_manager import HashtagStatsManager -------------------------------------------------------------------------------- /Chapter06/requirements/base.in: -------------------------------------------------------------------------------- 1 | nameko 2 | redis 3 | jinja2 4 | sqlalchemy 5 | nameko-sqlalchemy 6 | bcrypt 7 | flask 8 | psycopg2 9 | -------------------------------------------------------------------------------- /Chapter03/core/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .models import Config 2 | from .models import RequestToken 3 | from .models import RequestAuth 4 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/alignment.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class Alignment(Enum): 5 | LEFT = auto() 6 | RIGHT = auto() -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/request_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class RequestType(Enum): 5 | GET = auto() 6 | PUT = auto() -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .request import fetch_exchange_rates_by_currency 2 | from .db import DbClient 3 | from .currency import Currency 4 | -------------------------------------------------------------------------------- /Chapter03/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Click here to authorize 6 | 7 | -------------------------------------------------------------------------------- /Chapter03/core/twitter/hashtag.py: -------------------------------------------------------------------------------- 1 | class Hashtag: 2 | def __init__(self, name): 3 | self.name = name 4 | self.total = 0 5 | self.refresh_url = None 6 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Chapter02/musicterminal/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Click here to authorize 6 | 7 | -------------------------------------------------------------------------------- /Chapter05/config.yaml: -------------------------------------------------------------------------------- 1 | AMQP_URI: 'pyamqp://guest:guest@localhost' 2 | WEB_SERVER_ADDRESS: '0.0.0.0:8000' 3 | rpc_exchange: 'nameko-rpc' 4 | REDIS_URL: 'redis://localhost:6379/0' 5 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/images/placeholder.png -------------------------------------------------------------------------------- /Chapter08/gamestore/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/images/placeholder.png -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/auth_method.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class AuthMethod(Enum): 5 | CLIENT_CREDENTIALS = auto() 6 | AUTHORIZATION_CODE = auto() -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/search_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class SearchType(Enum): 5 | ARTIST = 1 6 | ALBUM = 2 7 | PLAYLIST = 3 8 | TRACK = 4 -------------------------------------------------------------------------------- /Chapter03/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import read_config 2 | from .request import execute_request 3 | from .cmdline_parser import parse_commandline_args 4 | from .runner import Runner -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/notification_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class NotificationType(Enum): 5 | ORDER_RECEIVED = auto() 6 | ORDER_SHIPPED = auto() -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/base_enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class BaseEnum(Enum): 5 | 6 | def _generate_next_value_(name, start, count, last_value): 7 | return name 8 | -------------------------------------------------------------------------------- /Chapter03/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | verify_ssl = true 5 | url = "https://pypi.python.org/simple" 6 | 7 | 8 | [packages] 9 | 10 | 11 | 12 | [dev-packages] 13 | 14 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/unit.py: -------------------------------------------------------------------------------- 1 | from enum import auto, unique 2 | from .base_enum import BaseEnum 3 | 4 | 5 | @unique 6 | class Unit(BaseEnum): 7 | CELSIUS = auto() 8 | FAHRENHEIT = auto() 9 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth_method import AuthMethod 2 | from .authorization import Authorization 3 | from .auth import authenticate 4 | from .auth import get_auth_key 5 | 6 | -------------------------------------------------------------------------------- /Chapter05/readme.md: -------------------------------------------------------------------------------- 1 | # TempMessenger 2 | 3 | Amend the config to point to your redis instance and rabbitmq instance. 4 | 5 | To run, simply execute: 6 | ``` 7 | nameko run temp_messenger.service --config config.yaml 8 | ``` 9 | -------------------------------------------------------------------------------- /Chapter06/readme.md: -------------------------------------------------------------------------------- 1 | # TempMessenger 2 | 3 | Amend the config to point to your redis instance and rabbitmq instance. 4 | 5 | To run, simply execute: 6 | ``` 7 | nameko run temp_messenger.service --config config.yaml 8 | ``` 9 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/forecast_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | 3 | 4 | @unique 5 | class ForecastType(Enum): 6 | TODAY = 'today' 7 | FIVEDAYS = '5day' 8 | TENDAYS = '10day' 9 | WEEKEND = 'weekend' 10 | -------------------------------------------------------------------------------- /Chapter09/notifier/templates/order_shipped_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hi, {{customer_name}}!

6 |

We just want to let you know that your order is on its way!

7 | 8 | -------------------------------------------------------------------------------- /Chapter07/gamestore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "description": "Retro game store website", 5 | "dependencies": { 6 | "bootstrap": "^3.3.7", 7 | "font-awesome": "^4.7.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter08/gamestore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "description": "Retro game store website", 5 | "dependencies": { 6 | "bootstrap": "^3.3.7", 7 | "font-awesome": "^4.7.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/authorization.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | Authorization = namedtuple('Authorization', [ 5 | 'access_token', 6 | 'token_type', 7 | 'expires_in', 8 | 'scope', 9 | 'refresh_token', 10 | ]) -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class Status(Enum): 5 | Received = auto() 6 | Processing = auto() 7 | Payment_Complete = auto() 8 | Shipping = auto() 9 | Completed = auto() 10 | Cancelled = auto() -------------------------------------------------------------------------------- /Chapter02/musicterminal/config.yaml: -------------------------------------------------------------------------------- 1 | client_id: '' 2 | client_secret: '' 3 | access_token_url: 'https://accounts.spotify.com/api/token' 4 | auth_url: 'http://accounts.spotify.com/authorize' 5 | api_version: 'v1' 6 | api_url: 'https://api.spotify.com' 7 | auth_method: 'AUTHORIZATION_CODE' 8 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class Status(Enum): 5 | Received = auto() 6 | Processing = auto() 7 | Payment_Complete = auto() 8 | Shipping = auto() 9 | Completed = auto() 10 | Cancelled = auto() 11 | -------------------------------------------------------------------------------- /Chapter04/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | url = "https://pypi.python.org/simple" 5 | verify_ssl = true 6 | 7 | 8 | [requires] 9 | 10 | python_version = "3.6" 11 | 12 | 13 | [dev-packages] 14 | 15 | 16 | 17 | [packages] 18 | 19 | requests = "*" 20 | pymongo = "*" 21 | -------------------------------------------------------------------------------- /Chapter06/setup_db.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | 3 | from temp_messenger.dependencies.users import User 4 | 5 | engine = create_engine( 6 | 'postgresql+psycopg2://postgres:secret@localhost/' 7 | 'users?client_encoding=utf8' 8 | ) 9 | 10 | User.metadata.create_all(engine) 11 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Order 4 | from .models import OrderCustomer 5 | from .models import OrderItems 6 | 7 | 8 | admin.site.register(OrderCustomer) 9 | admin.site.register(OrderItems) 10 | admin.site.register(Order) 11 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import GamePlatform 4 | from .models import Game 5 | from .models import PriceList 6 | 7 | admin.autodiscover() 8 | 9 | admin.site.register(GamePlatform) 10 | admin.site.register(Game) 11 | admin.site.register(PriceList) 12 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import GamePlatform 4 | from .models import Game 5 | from .models import PriceList 6 | 7 | admin.autodiscover() 8 | 9 | admin.site.register(GamePlatform) 10 | admin.site.register(Game) 11 | admin.site.register(PriceList) 12 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/set_unit_action.py: -------------------------------------------------------------------------------- 1 | from argparse import Action 2 | 3 | from weatherterm.core import Unit 4 | 5 | 6 | class SetUnitAction(Action): 7 | 8 | def __call__(self, parser, namespace, values, option_string=None): 9 | unit = Unit[values.upper()] 10 | setattr(namespace, self.dest, unit) -------------------------------------------------------------------------------- /Chapter06/requirements/test.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/test.txt requirements/test.in 6 | # 7 | fakeredis==0.8.2 8 | py==1.4.34 # via pytest 9 | pytest==3.2.2 10 | redis==2.10.6 # via fakeredis 11 | -------------------------------------------------------------------------------- /Chapter09/notifier/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | url = "https://pypi.python.org/simple" 5 | verify_ssl = true 6 | 7 | 8 | [dev-packages] 9 | 10 | 11 | 12 | [requires] 13 | 14 | python_version = "3.6" 15 | 16 | 17 | [packages] 18 | 19 | flask = "*" 20 | requests = "*" 21 | "boto3" = "*" 22 | botocore = "*" 23 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .unit import Unit 2 | from .base_enum import BaseEnum 3 | from .unit_converter import UnitConverter 4 | from .forecast_type import ForecastType 5 | from .forecast import Forecast 6 | from .request import Request 7 | 8 | from .set_unit_action import SetUnitAction 9 | 10 | from .mapper import Mapper 11 | -------------------------------------------------------------------------------- /Chapter03/config.yaml: -------------------------------------------------------------------------------- 1 | consumer_key: '' 2 | consumer_secret: '' 3 | request_token_url: 'https://api.twitter.com/oauth/request_token' 4 | authorize_url: 'https://api.twitter.com/oauth/authorize' 5 | access_token_url: 'https://api.twitter.com/oauth/access_token' 6 | api_version: '1.1' 7 | search_endpoint: 'https://api.twitter.com/1.1/search/tweets.json' 8 | -------------------------------------------------------------------------------- /Chapter03/core/app_logger.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from logging.config import fileConfig 4 | 5 | 6 | def get_logger(): 7 | core_dir = os.path.dirname(os.path.abspath(__file__)) 8 | file_path = os.path.join(core_dir, '..', 'logconfig.ini') 9 | fileConfig(file_path) 10 | return logging.getLogger('twitterVotesLogger') 11 | -------------------------------------------------------------------------------- /Chapter06/config.yaml: -------------------------------------------------------------------------------- 1 | AMQP_URI: 'pyamqp://guest:guest@localhost' 2 | WEB_SERVER_ADDRESS: '0.0.0.0:8000' 3 | rpc_exchange: 'nameko-rpc' 4 | REDIS_URL: 'redis://localhost:6379/0' 5 | DB_URIS: 6 | user_service:Base: 7 | "postgresql+psycopg2://postgres:secret@localhost/\ 8 | users?client_encoding=utf8" 9 | FLASK_SECRET_KEY: 'my-super-secret-flask-key' 10 | -------------------------------------------------------------------------------- /Chapter08/microservices/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | verify_ssl = true 4 | url = "https://pypi.python.org/simple" 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | Django = "*" 11 | djangorestframework = "*" 12 | python-dateutil = "*" 13 | requests = "*" 14 | 15 | 16 | [requires] 17 | 18 | python_version = "3.6" 19 | 20 | 21 | [dev-packages] 22 | 23 | -------------------------------------------------------------------------------- /Chapter09/microservices/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | verify_ssl = true 4 | url = "https://pypi.python.org/simple" 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | Django = "*" 11 | djangorestframework = "*" 12 | python-dateutil = "*" 13 | requests = "*" 14 | 15 | 16 | [requires] 17 | 18 | python_version = "3.6" 19 | 20 | 21 | [dev-packages] 22 | 23 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Login

4 |
5 | 6 | 7 | 8 |
9 | {% if login_error %} 10 |

Bad log in

11 | {% endif %} 12 | 13 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .exceptions import BadRequestError 2 | from .config import read_config 3 | 4 | from .search_type import SearchType 5 | 6 | from .search import search_album 7 | from .search import search_artist 8 | from .search import search_playlist 9 | from .search import search_track 10 | 11 | from .artist import get_artist_albums 12 | from .album import get_album_tracks 13 | from .player import play -------------------------------------------------------------------------------- /Chapter07/gamestore/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | url = "https://pypi.python.org/simple" 5 | verify_ssl = true 6 | 7 | 8 | [packages] 9 | 10 | django = "*" 11 | jedi = "*" 12 | pillow = "*" 13 | requests = "*" 14 | 15 | 16 | [dev-packages] 17 | 18 | jedi = "*" 19 | rope = "*" 20 | importmagic = "*" 21 | "autopep8" = "*" 22 | yapf = "*" 23 | "flake8" = "*" 24 | 25 | 26 | [requires] 27 | 28 | python_version = "3.6" 29 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/all_games.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |

All games

6 |
7 | 8 | {% if games %} 9 | {% include 'main/games-list.html' with gameslist=games 10 | highlight_games=True%} 11 | {% else %} 12 |
13 |

There's no promoted games available at the moment

14 |
15 | {% endif %} 16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | url = "https://pypi.python.org/simple" 5 | verify_ssl = true 6 | 7 | 8 | [packages] 9 | 10 | django = "*" 11 | jedi = "*" 12 | pillow = "*" 13 | requests = "*" 14 | 15 | 16 | [dev-packages] 17 | 18 | jedi = "*" 19 | rope = "*" 20 | importmagic = "*" 21 | "autopep8" = "*" 22 | yapf = "*" 23 | "flake8" = "*" 24 | 25 | 26 | [requires] 27 | 28 | python_version = "3.6" 29 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/all_games.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |

All games

6 |
7 | 8 | {% if games %} 9 | {% include 'main/games-list.html' with gameslist=games 10 | highlight_games=True%} 11 | {% else %} 12 |
13 |

There's no promoted games available at the moment

14 |
15 | {% endif %} 16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/highlighted.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |

Highlighted games

6 |
7 | 8 | {% if games %} 9 | {% include 'main/games-list.html' with gameslist=games highlight_promoted=False%} 10 | {% else %} 11 |
12 |

There's no promoted games available at the moment

13 |
14 | {% endif %} 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 |

Signup

7 | 8 |
9 | {% csrf_token %} 10 | 11 | {{ form }} 12 | 13 | 14 | 15 |
16 |
17 | 18 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/highlighted.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |

Highlighted games

6 |
7 | 8 | {% if games %} 9 | {% include 'main/games-list.html' with gameslist=games highlight_promoted=False%} 10 | {% else %} 11 |
12 |

There's no promoted games available at the moment

13 |
14 | {% endif %} 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 |

Signup

7 | 8 |
9 | {% csrf_token %} 10 | 11 | {{ form }} 12 | 13 | 14 | 15 |
16 |
17 | 18 | {% endblock %} -------------------------------------------------------------------------------- /Chapter05/requirements/test.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/test.txt requirements/test.in 6 | # 7 | attrs==17.4.0 # via pytest 8 | fakeredis==0.9.0 9 | pluggy==0.6.0 # via pytest 10 | py==1.5.2 # via pytest 11 | pytest==3.4.0 12 | redis==2.10.6 # via fakeredis 13 | six==1.11.0 # via pytest 14 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0003_auto_20180215_2147.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0002_pricelist'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='game', 15 | old_name='imagelink', 16 | new_name='image', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/create_account_success.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |

Your account have been successfully created!

12 | 13 | Click here to login 14 | 15 |
16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0003_auto_20180215_2147.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0002_pricelist'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='game', 15 | old_name='imagelink', 16 | new_name='image', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/create_account_success.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |

Your account have been successfully created!

12 | 13 | Click here to login 14 | 15 |
16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /Chapter07/gamestore/gamestore/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for gamestore project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Chapter08/gamestore/gamestore/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for gamestore project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/order/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for order project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/order/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for order project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/templates/sign_up.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Sign up

5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 | {% if error_message %} 13 |

{{ error_message }}

14 | {% endif %} 15 | 16 | -------------------------------------------------------------------------------- /Chapter03/logconfig.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,twitterVotesLogger 3 | 4 | [handlers] 5 | keys=consoleHandler 6 | 7 | [formatters] 8 | keys=simpleFormatter 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=consoleHandler 13 | 14 | [logger_twitterVotesLogger] 15 | level=INFO 16 | handlers=consoleHandler 17 | qualname=twitterVotesLogger 18 | 19 | 20 | [handler_consoleHandler] 21 | class=StreamHandler 22 | level=INFO 23 | formatter=simpleFormatter 24 | args=(sys.stdout,) 25 | 26 | [formatter_simpleFormatter] 27 | format=[%(levelname)s] %(asctime)s - %(message)s 28 | datefmt=%Y-%m-%d %H:%M:%S -------------------------------------------------------------------------------- /Chapter07/gamestore/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bootstrap": { 8 | "version": "3.3.7", 9 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", 10 | "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" 11 | }, 12 | "font-awesome": { 13 | "version": "4.7.0", 14 | "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", 15 | "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter08/gamestore/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bootstrap": { 8 | "version": "3.3.7", 9 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", 10 | "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" 11 | }, 12 | "font-awesome": { 13 | "version": "4.7.0", 14 | "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", 15 | "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/menu_item.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid1 2 | 3 | 4 | class MenuItem: 5 | def __init__(self, label, data, selected=False): 6 | self.id = str(uuid1()) 7 | self.data = data 8 | self.label = label 9 | 10 | def return_id(): 11 | return self.data['id'], self.data['uri'] 12 | 13 | self.action = return_id 14 | self.selected = selected 15 | 16 | def __eq__(self, other): 17 | return self.id == other.id 18 | 19 | def __len__(self): 20 | return len(self.label) 21 | 22 | def __str__(self): 23 | return self.label -------------------------------------------------------------------------------- /Chapter07/gamestore/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter08/gamestore/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/user_service.py: -------------------------------------------------------------------------------- 1 | from nameko.rpc import rpc 2 | 3 | from .dependencies.users import UserStore 4 | 5 | 6 | class UserService: 7 | 8 | name = 'user_service' 9 | 10 | user_store = UserStore() 11 | 12 | @rpc 13 | def create_user(self, first_name, last_name, email, password): 14 | self.user_store.create( 15 | first_name=first_name, 16 | last_name=last_name, 17 | email=email, 18 | password=password, 19 | ) 20 | 21 | @rpc 22 | def authenticate_user(self, email, password): 23 | self.user_store.authenticate(email, password) 24 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0004_auto_20180215_2156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0003_auto_20180215_2147'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='game', 15 | options={'ordering': ['-highlighted', 'name']}, 16 | ), 17 | migrations.RenameField( 18 | model_name='game', 19 | old_name='promoted', 20 | new_name='highlighted', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0004_auto_20180215_2156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0003_auto_20180215_2147'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='game', 15 | options={'ordering': ['-highlighted', 'name']}, 16 | ), 17 | migrations.RenameField( 18 | model_name='game', 19 | old_name='promoted', 20 | new_name='highlighted', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/album.py: -------------------------------------------------------------------------------- 1 | from .parameter import prepare_params 2 | from .request import execute_request 3 | 4 | 5 | def get_album_tracks(album_id, auth, params=None): 6 | 7 | if album_id is None or album_id is '': 8 | raise AttributeError( 9 | 'Parameter `album_id` cannot be `None` or empty.') 10 | 11 | url_template = '{base_url}/{area}/{albumid}/{postfix}{query}' 12 | url_params = { 13 | 'query': prepare_params(params), 14 | 'area': 'albums', 15 | 'albumid': album_id, 16 | 'postfix': 'tracks', 17 | } 18 | 19 | return execute_request(url_template, auth, url_params) -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/config.py: -------------------------------------------------------------------------------- 1 | from .config_error import ConfigError 2 | from currency_converter.core import DbClient 3 | 4 | 5 | def get_config(): 6 | config = None 7 | 8 | with DbClient('exchange_rates', 'config') as db: 9 | config = db.find_one() 10 | 11 | if config is None: 12 | error_message = ('It was not possible to get your base currency, that ' 13 | 'probably happened because it have not been ' 14 | 'set yet.\n Please, use the option ' 15 | '--setbasecurrency') 16 | raise ConfigError(error_message) 17 | 18 | return config 19 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/artist.py: -------------------------------------------------------------------------------- 1 | from .parameter import prepare_params 2 | from .request import execute_request 3 | 4 | 5 | def get_artist_albums(artist_id, auth, params=None): 6 | 7 | if artist_id is None or artist_id is "": 8 | raise AttributeError( 9 | 'Parameter `artist_id` cannot be `None` or empty.') 10 | 11 | url_template = '{base_url}/{area}/{artistid}/{postfix}{query}' 12 | url_params = { 13 | 'query': prepare_params(params), 14 | 'area': 'artists', 15 | 'artistid': artist_id, 16 | 'postfix': 'albums', 17 | } 18 | 19 | return execute_request(url_template, auth, url_params) -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/notifier.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | from order import settings 5 | 6 | from .notification_type import NotificationType 7 | 8 | 9 | def notify(order, notification_type): 10 | endpoint = ('notify/order-received/' 11 | if notification_type is NotificationType.ORDER_RECEIVED 12 | else 'notify/order-shipped/') 13 | 14 | header = { 15 | 'X-API-Key': settings.NOTIFIER_API_KEY 16 | } 17 | 18 | response = requests.post( 19 | f'{settings.NOTIFIER_BASEURL}/{endpoint}', 20 | json.dumps(order.data), 21 | headers=header 22 | ) 23 | 24 | return response -------------------------------------------------------------------------------- /Chapter07/gamestore/templates/_loginpartial.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated %} 2 | 16 | 17 | {% else %} 18 | 19 | 22 | 23 | {% endif %} -------------------------------------------------------------------------------- /Chapter08/gamestore/templates/_loginpartial.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated %} 2 | 16 | 17 | {% else %} 18 | 19 | 22 | 23 | {% endif %} -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/request.py: -------------------------------------------------------------------------------- 1 | import os 2 | from selenium import webdriver 3 | 4 | 5 | class Request: 6 | def __init__(self, base_url): 7 | self._driver_path = os.path.join(os.curdir, 'phantomjs/bin/phantomjs') 8 | self._base_url = base_url 9 | self._driver = webdriver.PhantomJS(self._driver_path) 10 | 11 | def fetch_data(self, forecast, area): 12 | url = self._base_url.format(forecast=forecast, area=area) 13 | self._driver.get(url) 14 | 15 | if self._driver.title == '404 Not Found': 16 | error_message = ('Could not find the area that you ' 17 | 'searching for') 18 | raise Exception(error_message) 19 | 20 | return self._driver.page_source 21 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidArgumentError(Exception): 2 | def __init__(self, argument_name): 3 | message = f'The argument {argument_name} is invalid' 4 | super().__init__(message) 5 | 6 | 7 | class OrderAlreadyCompletedError(Exception): 8 | def __init__(self, order): 9 | message = f'The order with ID: {order.id} is already completed.' 10 | super().__init__(message) 11 | 12 | 13 | class OrderAlreadyCancelledError(Exception): 14 | def __init__(self, order): 15 | message = f'The order with ID: {order.id} is already cancelled.' 16 | super().__init__(message) 17 | 18 | 19 | class OrderCancellationError(Exception): 20 | pass 21 | 22 | 23 | class OrderNotFoundError(Exception): 24 | pass 25 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from http import HTTPStatus 3 | import json 4 | 5 | 6 | def fetch_exchange_rates_by_currency(currency): 7 | response = requests.get(f'https://api.fixer.io/latest?base={currency}') 8 | 9 | if response.status_code == HTTPStatus.OK: 10 | return json.loads(response.text) 11 | elif response.status_code == HTTPStatus.NOT_FOUND: 12 | raise ValueError(f'Could not find the exchange rates for: {currency}.') 13 | elif response.status_code == HTTPStatus.BAD_REQUEST: 14 | raise ValueError(f'Invalid base currency value: {currency}') 15 | else: 16 | raise Exception((f'Something went wrong and we were unable to fetch' 17 | f' the exchange rates for: {currency}')) 18 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/mapper.py: -------------------------------------------------------------------------------- 1 | class Mapper: 2 | 3 | def __init__(self): 4 | self._mapping = {} 5 | 6 | def _add(self, source, dest): 7 | self._mapping[source] = dest 8 | 9 | def remap_key(self, source, dest): 10 | self._add(source, dest) 11 | 12 | def remap(self, itemslist): 13 | return [self._exec(item) for item in itemslist] 14 | 15 | def _exec(self, src_dict): 16 | dest = dict() 17 | 18 | if not src_dict: 19 | raise AttributeError('The source dictionary can be empty or None') 20 | 21 | for key, value in src_dict.items(): 22 | try: 23 | new_key = self._mapping[key] 24 | dest[new_key] = value 25 | except KeyError: 26 | dest[key] = value 27 | return dest -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidArgumentError(Exception): 2 | def __init__(self, argument_name): 3 | message = f'The argument {argument_name} is invalid' 4 | super().__init__(message) 5 | 6 | class OrderAlreadyCompletedError(Exception): 7 | def __init__(self, order): 8 | message = f'The order with ID: {order.id} is already completed.' 9 | super(OrderAlreadyCompletedError, self).__init__(message) 10 | 11 | 12 | class OrderAlreadyCancelledError(Exception): 13 | def __init__(self, order): 14 | message = f'The order with ID: {order.id} is already cancelled.' 15 | super(OrderAlreadyCancelledError, self).__init__(message) 16 | 17 | 18 | class OrderCancellationError(Exception): 19 | pass 20 | 21 | 22 | class OrderNotFoundError(Exception): 23 | pass -------------------------------------------------------------------------------- /Chapter03/core/runner.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | 3 | from rx import Observable 4 | 5 | 6 | class Runner: 7 | 8 | def __init__(self, on_success, on_error, on_complete): 9 | self._on_success = on_success 10 | self._on_error = on_error 11 | self._on_complete = on_complete 12 | 13 | def exec(self, func, items): 14 | observables = [] 15 | 16 | with concurrent.futures.ProcessPoolExecutor() as executor: 17 | for item in items.values(): 18 | _future = executor.submit(func, item) 19 | observables.append(Observable.from_future(_future)) 20 | 21 | all_observables = Observable.merge(observables) 22 | 23 | all_observables.subscribe(self._on_success, 24 | self._on_error, 25 | self._on_complete) 26 | -------------------------------------------------------------------------------- /Chapter07/gamestore/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 |
7 | 8 | {% csrf_token %} 9 | 10 | 11 | 12 | {{form.username}} 13 | 14 | {{form.password}} 15 | 17 |
18 |
19 | {% if form.non_field_errors %} 20 | 25 | {% endif %} 26 |
27 |
28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 |
7 | 8 | {% csrf_token %} 9 | 10 | 11 | 12 | {{form.username}} 13 | 14 | {{form.password}} 15 | 17 |
18 |
19 | {% if form.non_field_errors %} 20 | 25 | {% endif %} 26 |
27 |
28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /Chapter03/core/twitter/hashtagstats_manager.py: -------------------------------------------------------------------------------- 1 | from .hashtag import Hashtag 2 | 3 | 4 | class HashtagStatsManager: 5 | 6 | def __init__(self, hashtags): 7 | 8 | if not hashtags: 9 | raise AttributeError('hashtags must be provided') 10 | 11 | self._hashtags = {hashtag: Hashtag(hashtag) for hashtag in hashtags} 12 | 13 | def update(self, data): 14 | 15 | hashtag, results = data 16 | 17 | metadata = results.get('search_metadata') 18 | refresh_url = metadata.get('refresh_url') 19 | statuses = results.get('statuses') 20 | 21 | total = len(statuses) 22 | 23 | if total > 0: 24 | self._hashtags.get(hashtag.name).total += total 25 | self._hashtags.get(hashtag.name).refresh_url = refresh_url 26 | 27 | @property 28 | def hashtags(self): 29 | return self._hashtags 30 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0002_pricelist.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='PriceList', 16 | fields=[ 17 | ('added_at', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 20 | ('game', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='main.Game')), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0002_pricelist.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='PriceList', 16 | fields=[ 17 | ('added_at', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 20 | ('game', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='main.Game')), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | from django.contrib.auth.views import login 4 | from django.contrib.auth.views import logout 5 | from .forms import AuthenticationForm 6 | 7 | 8 | urlpatterns = [ 9 | path(r'', views.index, name='index'), 10 | path(r'accounts/login/', login, { 11 | 'template_name': 'login.html', 12 | 'authentication_form': AuthenticationForm 13 | }, name='login'), 14 | path(r'accounts/logout/', logout, { 15 | 'next_page': '/' 16 | }, name='logout'), 17 | path(r'accounts/signup/', views.signup, name='signup'), 18 | path(r'games-list/highlighted/', views.show_highlighted_games), 19 | path(r'games-list/all/', views.show_all_games), 20 | path(r'cart/', views.ShoppingCartEditView.as_view(), name='user-cart'), 21 | path(r'cart/add//', views.add_to_cart), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter07/gamestore/gamestore/urls.py: -------------------------------------------------------------------------------- 1 | """gamestore URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('main.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter08/gamestore/gamestore/urls.py: -------------------------------------------------------------------------------- 1 | """gamestore URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('main.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/order/urls.py: -------------------------------------------------------------------------------- 1 | """order URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/', include('main.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/order/urls.py: -------------------------------------------------------------------------------- 1 | """order URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/', include('main.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/dependencies/jinja2.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, PackageLoader, select_autoescape 2 | from nameko.extensions import DependencyProvider 3 | 4 | 5 | HOMEPAGE = 'home.html' 6 | 7 | 8 | class Jinja2(DependencyProvider): 9 | 10 | def setup(self): 11 | self.template_renderer = TemplateRenderer( 12 | 'temp_messenger', 'templates' 13 | ) 14 | 15 | def get_dependency(self, worker_ctx): 16 | return self.template_renderer 17 | 18 | 19 | class TemplateRenderer: 20 | 21 | def __init__(self, package_name, template_dir): 22 | self.template_env = Environment( 23 | loader=PackageLoader(package_name, template_dir), 24 | autoescape=select_autoescape(['html']) 25 | ) 26 | 27 | def render_home(self, messages): 28 | template = self.template_env.get_template('home.html') 29 | return template.render(messages=messages) 30 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/dependencies/jinja2.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, PackageLoader, select_autoescape 2 | from nameko.extensions import DependencyProvider 3 | 4 | 5 | HOMEPAGE = 'home.html' 6 | 7 | 8 | class Jinja2(DependencyProvider): 9 | 10 | def setup(self): 11 | self.template_renderer = TemplateRenderer( 12 | 'temp_messenger', 'templates' 13 | ) 14 | 15 | def get_dependency(self, worker_ctx): 16 | return self.template_renderer 17 | 18 | 19 | class TemplateRenderer: 20 | 21 | def __init__(self, package_name, template_dir): 22 | self.template_env = Environment( 23 | loader=PackageLoader(package_name, template_dir), 24 | autoescape=select_autoescape(['html']) 25 | ) 26 | 27 | def render_home(self, messages): 28 | template = self.template_env.get_template('home.html') 29 | return template.render(messages=messages) 30 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/player.py: -------------------------------------------------------------------------------- 1 | from .parameter import prepare_params 2 | from .request import execute_request 3 | 4 | from .request_type import RequestType 5 | 6 | 7 | def play(track_uri, auth, params=None): 8 | 9 | if track_uri is None or track_uri is '': 10 | raise AttributeError( 11 | 'Parameter `track_uri` cannot be `None` or empty.') 12 | 13 | url_template = '{base_url}/{area}/{postfix}' 14 | url_params = { 15 | 'query': prepare_params(params), 16 | 'area': 'me', 17 | 'postfix': 'player/play', 18 | } 19 | 20 | _payload = { 21 | 'uris': [track_uri], 22 | 'offset': {'uri': track_uri} 23 | } 24 | 25 | return execute_request(url_template, 26 | auth, 27 | url_params, 28 | request_type=RequestType.PUT, 29 | payload=_payload) 30 | -------------------------------------------------------------------------------- /Chapter09/notifier/templates/order_received_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hi, {{customer_name}}!

6 |

Thank you so much for your order

7 |

8 |

Order id: {{order_id}}

9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for item in order_items %} 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
ItemQuantityPrice per unit
{{item.name}}{{item.quantity}}${{item.price_per_unit}}
28 |
29 | Total: ${{total_purchased}} 30 |
31 | 32 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Order 4 | from .models import OrderCustomer 5 | from .models import OrderItems 6 | 7 | from .notifier import notify 8 | from .notification_type import NotificationType 9 | from .serializers import OrderSerializer 10 | from .status import Status 11 | 12 | class OrderAdmin(admin.ModelAdmin): 13 | 14 | def save_model(self, request, obj, form, change): 15 | order_current_status = Status(obj.status) 16 | status_changed = 'status' in form.changed_data 17 | 18 | if (status_changed and order_current_status is Status.Shipping): 19 | notify(OrderSerializer(obj), NotificationType.ORDER_SHIPPED) 20 | 21 | super(OrderAdmin, self).save_model(request, obj, form, change) 22 | 23 | 24 | admin.site.register(OrderCustomer) 25 | admin.site.register(OrderItems) 26 | admin.site.register(Order, OrderAdmin) 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Chapter03/core/models/models.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | Config = namedtuple('Config', ['consumer_key', 4 | 'consumer_secret', 5 | 'request_token_url', 6 | 'access_token_url', 7 | 'authorize_url', 8 | 'api_version', 9 | 'search_endpoint', ]) 10 | 11 | 12 | RequestToken = namedtuple('RequestToken', ['oauth_token', 13 | 'oauth_token_secret', 14 | 'oauth_callback_confirmed']) 15 | 16 | 17 | RequestAuth = namedtuple('RequestAuth', ['oauth_token', 18 | 'oauth_token_secret', 19 | 'user_id', 20 | 'screen_name', 21 | 'x_auth_expires', ]) 22 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/parameter.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlencode 2 | 3 | 4 | def validate_params(params, required=None): 5 | 6 | if required is None: 7 | return 8 | 9 | partial = {x: x in params.keys() for x in required} 10 | not_supplied = [x for x in partial.keys() if not partial[x]] 11 | 12 | if not_supplied: 13 | msg = f'The parameter(s) `{", ".join(not_supplied)}` are required' 14 | raise AttributeError(msg) 15 | 16 | 17 | def prepare_params(params, required=None): 18 | 19 | if params is None and required is not None: 20 | msg = f'The parameter(s) `{", ".join(required)}` are required' 21 | raise ValueError(msg) 22 | elif params is None and required is None: 23 | return '' 24 | else: 25 | validate_params(params, required) 26 | 27 | query = urlencode( 28 | '&'.join([f'{key}={value}' for key, value in params.values()]) 29 | ) 30 | 31 | return f'?{query}' 32 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/message_service.py: -------------------------------------------------------------------------------- 1 | from nameko.rpc import rpc 2 | from .dependencies.messages import MessageStore 3 | 4 | 5 | class MessageService: 6 | 7 | name = 'message_service' 8 | 9 | message_store = MessageStore() 10 | 11 | @rpc 12 | def get_message(self, message_id): 13 | return self.message_store.get_message(message_id) 14 | 15 | @rpc 16 | def save_message(self, email, message): 17 | message_id = self.message_store.save_message( 18 | email, message 19 | ) 20 | return message_id 21 | 22 | @rpc 23 | def get_all_messages(self): 24 | messages = self.message_store.get_all_messages() 25 | sorted_messages = sort_messages_by_expiry(messages) 26 | return sorted_messages 27 | 28 | 29 | def sort_messages_by_expiry(messages, reverse=False): 30 | return sorted( 31 | messages, 32 | key=lambda message: message['expires_in'], 33 | reverse=reverse 34 | ) 35 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | from django.contrib.auth.views import login 4 | from django.contrib.auth.views import logout 5 | from .forms import AuthenticationForm 6 | 7 | 8 | urlpatterns = [ 9 | path(r'', views.index, name='index'), 10 | path(r'accounts/login/', login, { 11 | 'template_name': 'login.html', 12 | 'authentication_form': AuthenticationForm 13 | }, name='login'), 14 | path(r'accounts/logout/', logout, { 15 | 'next_page': '/' 16 | }, name='logout'), 17 | path(r'accounts/signup/', views.signup, name='signup'), 18 | path(r'games-list/highlighted/', views.show_highlighted_games), 19 | path(r'games-list/all/', views.show_all_games), 20 | path(r'cart/', views.ShoppingCartEditView.as_view(), name='user-cart'), 21 | path(r'cart/add//', views.add_to_cart), 22 | path(r'cart/send', views.send_cart), 23 | path(r'my-orders/', views.my_orders), 24 | ] 25 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | {% if highlighted_games_list %} 5 |
6 |
7 |

Highlighted games

9 |
10 |
11 | {% include 'main/games-list.html' with gameslist=highlighted_games_list highlight_games=False%} 12 | {% if show_more_link_highlighted %} 13 |

14 | See more items 15 |

16 | {% endif %} 17 |
18 |
19 | {% endif %} 20 | 21 | {% if games_list %} 22 | {% include 'main/games-list.html' with gameslist=games_list highlight_games=False%} 23 | {% if show_more_link_games %} 24 |

25 | See all items 26 |

27 | {% endif %} 28 | {% endif %} 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | {% if highlighted_games_list %} 5 |
6 |
7 |

Highlighted games

9 |
10 |
11 | {% include 'main/games-list.html' with gameslist=highlighted_games_list highlight_games=False%} 12 | {% if show_more_link_highlighted %} 13 |

14 | See more items 15 |

16 | {% endif %} 17 |
18 |
19 | {% endif %} 20 | 21 | {% if games_list %} 22 | {% include 'main/games-list.html' with gameslist=games_list highlight_games=False%} 23 | {% if show_more_link_games %} 24 |

25 | See all items 26 |

27 | {% endif %} 28 | {% endif %} 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/parser_loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import inspect 4 | 5 | 6 | def _get_parser_list(dirname): 7 | files = [f.replace('.py', '') 8 | for f in os.listdir(dirname) 9 | if not f.startswith('__')] 10 | return files 11 | 12 | 13 | def _import_parsers(parserfiles): 14 | m = re.compile('.+parser$', re.I) 15 | _modules = __import__('weatherterm.parsers', 16 | globals(), 17 | locals(), 18 | parserfiles, 19 | 0) 20 | 21 | _parsers = [(k, v) for k, v in inspect.getmembers(_modules) 22 | if inspect.ismodule(v) and m.match(k)] 23 | _classes = dict() 24 | 25 | for k, v in _parsers: 26 | _classes.update({k: v for k, v in inspect.getmembers(v) 27 | if inspect.isclass(v) and m.match(k)}) 28 | return _classes 29 | 30 | 31 | def load(dirname): 32 | parserfiles = _get_parser_list(dirname) 33 | return _import_parsers(parserfiles) 34 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | from .search_type import SearchType 5 | from pytify.core import read_config 6 | 7 | 8 | def _search(criteria, auth, search_type): 9 | 10 | conf = read_config() 11 | 12 | if not criteria: 13 | raise AttributeError('Parameter `criteria` is required.') 14 | 15 | q_type = search_type.name.lower() 16 | url = f'{conf.base_url}/search?q={criteria}&type={q_type}' 17 | 18 | headers = {'Authorization': f'Bearer {auth.access_token}'} 19 | response = requests.get(url, headers=headers) 20 | 21 | return json.loads(response.text) 22 | 23 | 24 | def search_artist(criteria, auth): 25 | return _search(criteria, auth, SearchType.ARTIST) 26 | 27 | 28 | def search_album(criteria, auth): 29 | return _search(criteria, auth, SearchType.ALBUM) 30 | 31 | 32 | def search_playlist(criteria, auth): 33 | return _search(criteria, auth, SearchType.PLAYLIST) 34 | 35 | 36 | def search_track(criteria, auth): 37 | return _search(criteria, auth, SearchType.TRACK) -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/panel.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import curses.panel 3 | from uuid import uuid1 4 | 5 | 6 | class Panel: 7 | 8 | def __init__(self, title, dimensions): 9 | height, width, y, x = dimensions 10 | 11 | self._win = curses.newwin(height, width, y, x) 12 | self._win.box() 13 | self._panel = curses.panel.new_panel(self._win) 14 | self.title = title 15 | self._id = uuid1() 16 | 17 | self._set_title() 18 | 19 | self.hide() 20 | 21 | def hide(self): 22 | self._panel.hide() 23 | 24 | def _set_title(self): 25 | formatted_title = f' {self.title} ' 26 | self._win.addstr(0, 2, formatted_title, curses.A_REVERSE) 27 | 28 | def show(self): 29 | self._win.clear() 30 | self._win.box() 31 | self._set_title() 32 | curses.curs_set(0) 33 | self._panel.show() 34 | 35 | def is_visible(self): 36 | return not self._panel.hidden() 37 | 38 | def __eq__(self, other): 39 | return self._id == other._id 40 | 41 | -------------------------------------------------------------------------------- /Chapter05/requirements/base.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/base.txt requirements/base.in 6 | # 7 | amqp==1.4.9 # via kombu 8 | anyjson==0.3.3 # via kombu 9 | certifi==2018.1.18 # via requests 10 | chardet==3.0.4 # via requests 11 | enum-compat==0.0.2 # via eventlet 12 | eventlet==0.21.0 # via nameko 13 | greenlet==0.4.13 # via eventlet 14 | idna==2.6 # via requests 15 | jinja2==2.10 16 | kombu==3.0.37 # via nameko 17 | markupsafe==1.0 # via jinja2 18 | mock==2.0.0 # via nameko 19 | nameko==2.8.3 20 | path.py==10.5 # via nameko 21 | pbr==3.1.1 # via mock 22 | pyyaml==3.12 # via nameko 23 | redis==2.10.6 24 | requests==2.18.4 # via nameko 25 | six==1.11.0 # via mock, nameko 26 | urllib3==1.22 # via requests 27 | werkzeug==0.14.1 # via nameko 28 | wrapt==1.10.11 # via nameko 29 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/currency.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Currency(Enum): 5 | AUD = 'Australia Dollar' 6 | BGN = 'Bulgaria Lev' 7 | BRL = 'Brazil Real' 8 | CAD = 'Canada Dollar' 9 | CHF = 'Switzerland Franc' 10 | CNY = 'China Yuan/Renminbi' 11 | CZK = 'Czech Koruna' 12 | DKK = 'Denmark Krone' 13 | GBP = 'Great Britain Pound' 14 | HKD = 'Hong Kong Dollar' 15 | HRK = 'Croatia Kuna' 16 | HUF = 'Hungary Forint' 17 | IDR = 'Indonesia Rupiah' 18 | ILS = 'Israel New Shekel' 19 | INR = 'India Rupee' 20 | JPY = 'Japan Yen' 21 | KRW = 'South Korea Won' 22 | MXN = 'Mexico Peso' 23 | MYR = 'Malaysia Ringgit' 24 | NOK = 'Norway Kroner' 25 | NZD = 'New Zealand Dollar' 26 | PHP = 'Philippines Peso' 27 | PLN = 'Poland Zloty' 28 | RON = 'Romania New Lei' 29 | RUB = 'Russia Rouble' 30 | SEK = 'Sweden Krona' 31 | SGD = 'Singapore Dollar' 32 | THB = 'Thailand Baht' 33 | TRY = 'Turkish New Lira' 34 | USD = 'USA Dollar' 35 | ZAR = 'South Africa Rand' 36 | EUR = 'Euro' 37 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/my-orders.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |

Order history

6 | 7 | {% for order in orders %} 8 | 9 |
10 |
Order ID: {{order.id}}
11 |
Create date: {{ order.created_at }}
12 |
Status: {{order.status}}
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for item in order.items %} 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
Product nameQuantityPrice per unit
{{item.name}}{{item.quantity}}${{item.price_per_unit}}
31 |
32 |
Total amount:{{order.total}}
33 |
34 |
35 | {% endfor %} 36 | {% endblock %} -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | from .exceptions import BadRequestError 5 | from .config import read_config 6 | from .request_type import RequestType 7 | 8 | 9 | def execute_request( 10 | url_template, 11 | auth, 12 | params, 13 | request_type=RequestType.GET, 14 | payload=()): 15 | 16 | conf = read_config() 17 | 18 | params['base_url'] = conf.base_url 19 | 20 | url = url_template.format(**params) 21 | 22 | headers = { 23 | 'Authorization': f'Bearer {auth.access_token}' 24 | } 25 | 26 | if request_type is RequestType.GET: 27 | response = requests.get(url, headers=headers) 28 | else: 29 | response = requests.put(url, headers=headers, data=json.dumps(payload)) 30 | 31 | if not response.text: 32 | return response.text 33 | 34 | result = json.loads(response.text) 35 | 36 | if not response.ok: 37 | error = result['error'] 38 | raise BadRequestError( 39 | f'{error["message"]} (HTTP {error["status"]})') 40 | 41 | return result -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/games-list.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load humanize %} 3 | 4 | 5 |
6 | {% for game in gameslist %} 7 | {% if game.highlighted and highlight_games %} 8 |
9 | {% else %} 10 |
11 | {% endif %} 12 |
13 | 14 |
15 |
16 |

{{game.name}}

17 |

Release year: {{game.release_year}}

18 |

Developer: {{game.developer}}

19 |

Publisher: {{game.published_by}}

20 | {% if game.pricelist.price_per_unit %} 21 |

22 | Price: ${{game.pricelist.price_per_unit|floatformat:2|intcomma}} 23 |

24 | {% else %} 25 |

Price: Not available

26 | {% endif %} 27 |
28 | 30 | 31 | Add to cart 32 | 33 |
34 | {% endfor %} 35 |
36 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/games-list.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load humanize %} 3 | 4 | 5 |
6 | {% for game in gameslist %} 7 | {% if game.highlighted and highlight_games %} 8 |
9 | {% else %} 10 |
11 | {% endif %} 12 |
13 | 14 |
15 |
16 |

{{game.name}}

17 |

Release year: {{game.release_year}}

18 |

Developer: {{game.developer}}

19 |

Publisher: {{game.published_by}}

20 | {% if game.pricelist.price_per_unit %} 21 |

22 | Price: ${{game.pricelist.price_per_unit|floatformat:2|intcomma}} 23 |

24 | {% else %} 25 |

Price: Not available

26 | {% endif %} 27 |
28 | 30 | 31 | Add to cart 32 | 33 |
34 | {% endfor %} 35 |
36 | -------------------------------------------------------------------------------- /Chapter05/tests/test_dependencies/test_redis.py: -------------------------------------------------------------------------------- 1 | from mock import Mock, patch, call 2 | 3 | import pytest 4 | 5 | from temp_messenger.dependencies.redis import MessageStore 6 | 7 | 8 | @pytest.fixture 9 | def mock_strict_redis(): 10 | with patch('temp_messenger.dependencies.redis.StrictRedis') as redis: 11 | yield redis 12 | 13 | 14 | @pytest.fixture 15 | def message_store(): 16 | message_store = MessageStore() 17 | message_store.container = Mock() 18 | message_store.container.config = {'REDIS_URL': 'redis://redis/0'} 19 | 20 | return message_store 21 | 22 | 23 | def test_sets_up_redis_dependency(mock_strict_redis, message_store): 24 | message_store.setup() 25 | 26 | assert mock_strict_redis.from_url.call_args_list == [ 27 | call('redis://redis/0', decode_responses=True, charset='utf-8') 28 | ] 29 | 30 | 31 | def test_stop(mock_strict_redis, message_store): 32 | message_store.setup() 33 | message_store.stop() 34 | 35 | with pytest.raises(AttributeError): 36 | message_store.client 37 | 38 | 39 | def test_get_dependency(mock_strict_redis, message_store): 40 | message_store.setup() 41 | 42 | worker_ctx = Mock() 43 | assert message_store.get_dependency(worker_ctx) == message_store.client 44 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/unit_converter.py: -------------------------------------------------------------------------------- 1 | from .unit import Unit 2 | 3 | 4 | class UnitConverter: 5 | def __init__(self, parser_default_unit, dest_unit=None): 6 | self._parser_default_unit = parser_default_unit 7 | self.dest_unit = dest_unit 8 | self._convert_functions = { 9 | Unit.CELSIUS: self._to_celsius, 10 | Unit.FAHRENHEIT: self._to_fahrenheit, 11 | } 12 | 13 | def convert(self, temp): 14 | 15 | try: 16 | temperature = float(temp) 17 | except ValueError: 18 | return 0 19 | 20 | if (self.dest_unit == self._parser_default_unit or 21 | self.dest_unit is None): 22 | return self._format_results(temperature) 23 | 24 | func = self._convert_functions[self.dest_unit] 25 | result = func(temperature) 26 | 27 | return self._format_results(result) 28 | 29 | def _format_results(self, value): 30 | return int(value) if value.is_integer() else f'{value:.1f}' 31 | 32 | def _to_celsius(self, fahrenheit_temp): 33 | result = (fahrenheit_temp - 32) * 5 / 9 34 | 35 | return result 36 | 37 | def _to_fahrenheit(self, celsius_temp): 38 | result = (celsius_temp * 9 / 5) + 32 39 | 40 | return result 41 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | cancel_order, 5 | set_next_status, 6 | set_status, 7 | OrdersByCustomerView, 8 | IncompleteOrdersByCustomerView, 9 | CompletedOrdersByCustomerView, 10 | OrderByStatusView, 11 | CreateOrderView, 12 | ) 13 | 14 | urlpatterns = [ 15 | path( 16 | r'order/add/', 17 | CreateOrderView.as_view() 18 | ), 19 | path( 20 | r'customer//orders/get/', 21 | OrdersByCustomerView.as_view() 22 | ), 23 | path( 24 | r'customer//orders/incomplete/get/', 25 | IncompleteOrdersByCustomerView.as_view() 26 | ), 27 | path( 28 | r'customer//orders/complete/get/', 29 | CompletedOrdersByCustomerView.as_view() 30 | ), 31 | path( 32 | r'order//cancel', 33 | cancel_order 34 | ), 35 | path( 36 | r'order/status//get/', 37 | OrderByStatusView.as_view() 38 | ), 39 | path( 40 | r'order//status//set/', 41 | set_status 42 | ), 43 | path( 44 | r'order//status/next/', 45 | set_next_status 46 | ), 47 | ] -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | cancel_order, 5 | set_next_status, 6 | set_status, 7 | OrdersByCustomerView, 8 | IncompleteOrdersByCustomerView, 9 | CompletedOrdersByCustomerView, 10 | OrderByStatusView, 11 | CreateOrderView, 12 | ) 13 | 14 | urlpatterns = [ 15 | path( 16 | r'order/add/', 17 | CreateOrderView.as_view() 18 | ), 19 | path( 20 | r'customer//orders/get/', 21 | OrdersByCustomerView.as_view() 22 | ), 23 | path( 24 | r'customer//orders/incomplete/get/', 25 | IncompleteOrdersByCustomerView.as_view() 26 | ), 27 | path( 28 | r'customer//orders/complete/get/', 29 | CompletedOrdersByCustomerView.as_view() 30 | ), 31 | path( 32 | r'order//cancel', 33 | cancel_order 34 | ), 35 | path( 36 | r'order/status//get/', 37 | OrderByStatusView.as_view() 38 | ), 39 | path( 40 | r'order//status//set/', 41 | set_status 42 | ), 43 | path( 44 | r'order//status/next/', 45 | set_next_status 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /Chapter03/core/cmdline_parser.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from .app_logger import get_logger 4 | 5 | 6 | def validated_args(args): 7 | logger = get_logger() 8 | 9 | unique_hashtags = list(set(args.hashtags)) 10 | 11 | if len(unique_hashtags) < len(args.hashtags): 12 | logger.info(('Some hashtags passed as arguments were ' 13 | 'duplicated and are going to be ignored')) 14 | 15 | args.hashtags = unique_hashtags 16 | 17 | if len(args.hashtags) > 4: 18 | logger.error('Voting app accepts only 4 hashtags at the time') 19 | args.hashtags = args.hashtags[:4] 20 | 21 | return args 22 | 23 | 24 | def parse_commandline_args(): 25 | argparser = ArgumentParser( 26 | prog='twittervoting', 27 | description='Collect votes using twitter hashtags.') 28 | 29 | required = argparser.add_argument_group('require arguments') 30 | 31 | required.add_argument( 32 | '-ht', '--hashtags', 33 | nargs='+', 34 | required=True, 35 | dest='hashtags', 36 | help=('Space separated list specifying the ' 37 | 'hashtags that will be used for the voting.\n' 38 | 'Type the hashtags without the hash symbol.')) 39 | 40 | args = argparser.parse_args() 41 | 42 | return validated_args(args) 43 | -------------------------------------------------------------------------------- /Chapter06/requirements/base.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/base.txt requirements/base.in 6 | # 7 | amqp==1.4.9 # via kombu 8 | anyjson==0.3.3 # via kombu 9 | bcrypt==3.1.4 10 | certifi==2017.7.27.1 # via requests 11 | cffi==1.11.4 # via bcrypt 12 | chardet==3.0.4 # via requests 13 | click==6.7 # via flask 14 | enum-compat==0.0.2 # via eventlet 15 | eventlet==0.21.0 # via nameko 16 | flask==0.12.2 17 | greenlet==0.4.12 # via eventlet 18 | idna==2.6 # via requests 19 | itsdangerous==0.24 # via flask 20 | jinja2==2.9.6 21 | kombu==3.0.37 # via nameko 22 | markupsafe==1.0 # via jinja2 23 | mock==2.0.0 # via nameko 24 | nameko-sqlalchemy==1.0.0 25 | nameko==2.6.0 26 | path.py==10.4 # via nameko 27 | pbr==3.1.1 # via mock 28 | psycopg2==2.7.4 29 | pycparser==2.18 # via cffi 30 | pyyaml==3.12 # via nameko 31 | redis==2.10.6 32 | requests==2.18.4 # via nameko 33 | six==1.11.0 # via bcrypt, mock, nameko 34 | sqlalchemy==1.2.1 35 | urllib3==1.22 # via requests 36 | werkzeug==0.12.2 # via flask, nameko 37 | wrapt==1.10.11 # via nameko 38 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/view_helper.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, status 2 | from rest_framework.response import Response 3 | 4 | from django.http import HttpResponse 5 | 6 | from .exceptions import InvalidArgumentError 7 | from .exceptions import OrderAlreadyCancelledError 8 | from .exceptions import OrderAlreadyCompletedError 9 | 10 | from .serializers import OrderSerializer 11 | 12 | 13 | class OrderListAPIBaseView(generics.ListAPIView): 14 | serializer_class = OrderSerializer 15 | lookup_field = '' 16 | 17 | def get_queryset(self, lookup_field_id): 18 | pass 19 | 20 | def list(self, request, *args, **kwargs): 21 | try: 22 | result = self.get_queryset(kwargs.get(self.lookup_field, None)) 23 | except Exception as err: 24 | return Response(err, status=status.HTTP_400_BAD_REQUEST) 25 | 26 | serializer = OrderSerializer(result, many=True) 27 | return Response(serializer.data, status=status.HTTP_200_OK) 28 | 29 | 30 | def set_status_handler(set_status_delegate): 31 | try: 32 | set_status_delegate() 33 | except ( 34 | InvalidArgumentError, 35 | OrderAlreadyCancelledError, 36 | OrderAlreadyCompletedError) as err: 37 | return HttpResponse(err, status=status.HTTP_400_BAD_REQUEST) 38 | 39 | return HttpResponse(status=status.HTTP_204_NO_CONTENT) -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/view_helper.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, status 2 | from rest_framework.response import Response 3 | 4 | from django.http import HttpResponse 5 | 6 | from .exceptions import InvalidArgumentError 7 | from .exceptions import OrderAlreadyCancelledError 8 | from .exceptions import OrderAlreadyCompletedError 9 | 10 | from .serializers import OrderSerializer 11 | 12 | 13 | class OrderListAPIBaseView(generics.ListAPIView): 14 | serializer_class = OrderSerializer 15 | lookup_field = '' 16 | 17 | def get_queryset(self, lookup_field_id): 18 | pass 19 | 20 | def list(self, request, *args, **kwargs): 21 | try: 22 | result = self.get_queryset(kwargs.get(self.lookup_field, None)) 23 | except Exception as err: 24 | return Response(err, status=status.HTTP_400_BAD_REQUEST) 25 | 26 | serializer = OrderSerializer(result, many=True) 27 | return Response(serializer.data, status=status.HTTP_200_OK) 28 | 29 | 30 | def set_status_handler(set_status_delegate): 31 | try: 32 | set_status_delegate() 33 | except ( 34 | InvalidArgumentError, 35 | OrderAlreadyCancelledError, 36 | OrderAlreadyCompletedError) as err: 37 | return HttpResponse(err, status=status.HTTP_400_BAD_REQUEST) 38 | 39 | return HttpResponse(status=status.HTTP_204_NO_CONTENT) 40 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/db.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | 3 | 4 | class DbClient: 5 | 6 | def __init__(self, db_name, default_collection): 7 | self._db_name = db_name 8 | self._default_collection = default_collection 9 | self._db = None 10 | 11 | def connect(self): 12 | self._client = MongoClient('mongodb://127.0.0.1:27017/') 13 | self._db = self._client.get_database(self._db_name) 14 | 15 | def disconnect(self): 16 | self._client.close() 17 | 18 | def __enter__(self): 19 | self.connect() 20 | return self 21 | 22 | def __exit__(self, exec_type, exec_value, traceback): 23 | self.disconnect() 24 | 25 | if exec_type: 26 | raise exec_type(exec_value) 27 | 28 | return self 29 | 30 | def _get_collection(self): 31 | if self._default_collection is None: 32 | raise AttributeError('collection argument is required') 33 | 34 | return self._db[self._default_collection] 35 | 36 | def find_one(self, filter=None): 37 | collection = self._get_collection() 38 | return collection.find_one(filter) 39 | 40 | def update(self, filter, document, upsert=True): 41 | collection = self._get_collection() 42 | 43 | collection.find_one_and_update( 44 | filter, 45 | {'$set': document}, 46 | upsert=upsert) 47 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .core.cmdline_parser import parse_commandline_args 4 | from .config import get_config 5 | from .core import DbClient 6 | from .core import fetch_exchange_rates_by_currency 7 | 8 | 9 | def main(): 10 | args = parse_commandline_args() 11 | value = args.value 12 | dest_currency = args.dest_currency 13 | from_currency = args.from_currency 14 | 15 | config = get_config() 16 | base_currency = (from_currency 17 | if from_currency 18 | else config['base_currency']) 19 | 20 | with DbClient('exchange_rates', 'rates') as db: 21 | exchange_rates = db.find_one({'base': base_currency}) 22 | 23 | if exchange_rates is None: 24 | print(('Fetching exchange rates from fixer.io' 25 | f' [base currency: {base_currency}]')) 26 | 27 | try: 28 | response = fetch_exchange_rates_by_currency(base_currency) 29 | except Exception as e: 30 | sys.exit(f'Error: {e}') 31 | 32 | dest_rate = response['rates'][dest_currency] 33 | db.update({'base': base_currency}, response) 34 | else: 35 | dest_rate = exchange_rates['rates'][dest_currency] 36 | 37 | total = round(dest_rate * value, 2) 38 | print(f'{value} {base_currency} = {total} {dest_currency}') 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /Chapter03/core/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | 4 | from .models import Config 5 | from .models import RequestAuth 6 | 7 | 8 | def _read_yaml_file(filename, cls): 9 | core_dir = os.path.dirname(os.path.abspath(__file__)) 10 | file_path = os.path.join(core_dir, '..', filename) 11 | 12 | with open(file_path, mode='r', encoding='UTF-8') as file: 13 | config = yaml.load(file) 14 | return cls(**config) 15 | 16 | 17 | def read_config(): 18 | try: 19 | return _read_yaml_file('config.yaml', Config) 20 | except IOError as e: 21 | print(""" Error: couldn\'t file the configuration file `config.yaml` 22 | 'on your current directory. 23 | 24 | Default format is:', 25 | 26 | consumer_key: 'your_consumer_key' 27 | consumer_secret: 'your_consumer_secret' 28 | request_token_url: 'https://api.twitter.com/oauth/request_token' 29 | access_token_url: 'https://api.twitter.com/oauth/access_token' 30 | authorize_url: 'https://api.twitter.com/oauth/authorize' 31 | api_version: '1.1' 32 | search_endpoint: '' 33 | """) 34 | raise 35 | 36 | 37 | def read_reqauth(): 38 | try: 39 | return _read_yaml_file('.twitterauth', RequestAuth) 40 | except IOError as e: 41 | print(('It seems like you have not authorized the application.\n' 42 | 'In order to use your twitter data, please run the ' 43 | 'auth.py first.')) 44 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0005_shoppingcart_shoppingcartitem.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 22:57 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('main', '0004_auto_20180215_2156'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ShoppingCart', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='ShoppingCartItem', 25 | fields=[ 26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('quantity', models.IntegerField()), 28 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 29 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.ShoppingCart')), 30 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Game')), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0005_shoppingcart_shoppingcartitem.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 22:57 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('main', '0004_auto_20180215_2156'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ShoppingCart', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='ShoppingCartItem', 25 | fields=[ 26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('quantity', models.IntegerField()), 28 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 29 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.ShoppingCart')), 30 | ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Game')), 31 | ], 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .managers import OrderManager 3 | 4 | 5 | class OrderCustomer(models.Model): 6 | customer_id = models.IntegerField() 7 | name = models.CharField(max_length=100) 8 | email = models.CharField(max_length=100) 9 | 10 | class Order(models.Model): 11 | 12 | ORDER_STATUS = ( 13 | (1, 'Received'), 14 | (2, 'Processing'), 15 | (3, 'Payment complete'), 16 | (4, 'Shipping'), 17 | (5, 'Completed'), 18 | (6, 'Cancelled'), 19 | ) 20 | 21 | order_customer = models.ForeignKey( 22 | OrderCustomer, 23 | on_delete=models.CASCADE 24 | ) 25 | total = models.DecimalField( 26 | max_digits=9, 27 | decimal_places=2, 28 | default=0 29 | ) 30 | created_at = models.DateTimeField(auto_now_add=True) 31 | last_updated = models.DateTimeField(auto_now=True) 32 | status = models.IntegerField(choices=ORDER_STATUS, default='1') 33 | 34 | objects = OrderManager() 35 | 36 | 37 | class OrderItems(models.Model): 38 | class Meta: 39 | verbose_name_plural = 'Order items' 40 | 41 | product_id = models.IntegerField() 42 | name = models.CharField(max_length=200) 43 | quantity = models.IntegerField() 44 | price_per_unit = models.DecimalField( 45 | max_digits=9, 46 | decimal_places=2, 47 | default=0 48 | ) 49 | order = models.ForeignKey( 50 | Order, on_delete=models.CASCADE, related_name='items') -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .managers import OrderManager 3 | 4 | 5 | class OrderCustomer(models.Model): 6 | customer_id = models.IntegerField() 7 | name = models.CharField(max_length=100) 8 | email = models.CharField(max_length=100) 9 | 10 | 11 | class Order(models.Model): 12 | 13 | ORDER_STATUS = ( 14 | (1, 'Received'), 15 | (2, 'Processing'), 16 | (3, 'Payment complete'), 17 | (4, 'Shipping'), 18 | (5, 'Completed'), 19 | (6, 'Cancelled'), 20 | ) 21 | 22 | order_customer = models.ForeignKey( 23 | OrderCustomer, 24 | on_delete=models.CASCADE 25 | ) 26 | total = models.DecimalField( 27 | max_digits=9, 28 | decimal_places=2, 29 | default=0 30 | ) 31 | created_at = models.DateTimeField(auto_now_add=True) 32 | last_updated = models.DateTimeField(auto_now=True) 33 | status = models.IntegerField(choices=ORDER_STATUS, default='1') 34 | 35 | objects = OrderManager() 36 | 37 | 38 | class OrderItems(models.Model): 39 | class Meta: 40 | verbose_name_plural = 'Order items' 41 | 42 | product_id = models.IntegerField() 43 | name = models.CharField(max_length=200) 44 | quantity = models.IntegerField() 45 | price_per_unit = models.DecimalField( 46 | max_digits=9, 47 | decimal_places=2, 48 | default=0 49 | ) 50 | order = models.ForeignKey( 51 | Order, on_delete=models.CASCADE, related_name='items') 52 | -------------------------------------------------------------------------------- /Chapter06/tests/test_dependencies/test_messages.py: -------------------------------------------------------------------------------- 1 | from mock import Mock, patch, call 2 | 3 | import pytest 4 | 5 | from temp_messenger.dependencies.messages import MessageStore 6 | 7 | 8 | @pytest.fixture 9 | def mock_strict_redis(): 10 | with patch( 11 | 'temp_messenger.dependencies.messages.StrictRedis' 12 | ) as redis: 13 | yield redis 14 | 15 | 16 | @pytest.fixture 17 | def test_message_store(): 18 | test_message_store = MessageStore() 19 | test_message_store.container = Mock() 20 | test_message_store.container.config = { 21 | 'REDIS_URL': 'redis://redis/0' 22 | } 23 | 24 | return test_message_store 25 | 26 | 27 | def test_sets_up_redis_dependency( 28 | mock_strict_redis, test_message_store 29 | ): 30 | test_message_store.setup() 31 | 32 | assert test_message_store.redis_url == 'redis://redis/0' 33 | assert mock_strict_redis.from_url.call_args_list == [ 34 | call( 35 | 'redis://redis/0', 36 | decode_responses=True, 37 | charset='utf-8' 38 | ) 39 | ] 40 | 41 | 42 | def test_stop(mock_strict_redis, test_message_store): 43 | test_message_store.setup() 44 | test_message_store.stop() 45 | 46 | with pytest.raises(AttributeError): 47 | test_message_store.client 48 | 49 | 50 | def test_get_dependency(mock_strict_redis, test_message_store): 51 | test_message_store.setup() 52 | 53 | worker_ctx = Mock() 54 | assert test_message_store.get_dependency( 55 | worker_ctx 56 | ) == test_message_store.client 57 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:10 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Game', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ('release_year', models.IntegerField(null=True)), 21 | ('developer', models.CharField(max_length=100)), 22 | ('published_by', models.CharField(max_length=100)), 23 | ('imagelink', models.ImageField(default='images/placeholder.png', upload_to='images/')), 24 | ('promoted', models.BooleanField(default=False)), 25 | ], 26 | options={ 27 | 'ordering': ['-promoted', 'name'], 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='GamePlatform', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('name', models.CharField(max_length=100)), 35 | ], 36 | ), 37 | migrations.AddField( 38 | model_name='game', 39 | name='gameplatform', 40 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.GamePlatform'), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:10 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Game', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ('release_year', models.IntegerField(null=True)), 21 | ('developer', models.CharField(max_length=100)), 22 | ('published_by', models.CharField(max_length=100)), 23 | ('imagelink', models.ImageField(default='images/placeholder.png', upload_to='images/')), 24 | ('promoted', models.BooleanField(default=False)), 25 | ], 26 | options={ 27 | 'ordering': ['-promoted', 'name'], 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='GamePlatform', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('name', models.CharField(max_length=100)), 35 | ], 36 | ), 37 | migrations.AddField( 38 | model_name='game', 39 | name='gameplatform', 40 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.GamePlatform'), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /Chapter03/core/request.py: -------------------------------------------------------------------------------- 1 | import oauth2 as oauth 2 | import time 3 | from urllib.parse import parse_qsl 4 | import json 5 | 6 | import requests 7 | 8 | from .config import read_config 9 | from .config import read_reqauth 10 | 11 | 12 | def prepare_request(url, url_params): 13 | reqconfig = read_reqauth() 14 | config = read_config() 15 | 16 | token = oauth.Token( 17 | key=reqconfig.oauth_token, 18 | secret=reqconfig.oauth_token_secret) 19 | 20 | consumer = oauth.Consumer( 21 | key=config.consumer_key, 22 | secret=config.consumer_secret) 23 | 24 | params = { 25 | 'oauth_version': "1.0", 26 | 'oauth_nonce': oauth.generate_nonce(), 27 | 'oauth_timestamp': str(int(time.time())) 28 | } 29 | 30 | params['oauth_token'] = token.key 31 | params['oauth_consumer_key'] = consumer.key 32 | 33 | params.update(url_params) 34 | 35 | req = oauth.Request(method="GET", url=url, parameters=params) 36 | 37 | signature_method = oauth.SignatureMethod_HMAC_SHA1() 38 | req.sign_request(signature_method, consumer, token) 39 | 40 | return req.to_url() 41 | 42 | 43 | def execute_request(hashtag): 44 | config = read_config() 45 | 46 | if hashtag.refresh_url: 47 | refresh_url = hashtag.refresh_url[1:] 48 | url_params = dict(parse_qsl(refresh_url)) 49 | else: 50 | url_params = { 51 | 'q': f'#{hashtag.name}', 52 | 'result_type': 'mixed' 53 | } 54 | 55 | url = prepare_request(config.search_endpoint, url_params) 56 | 57 | data = requests.get(url) 58 | 59 | results = json.loads(data.text) 60 | 61 | return (hashtag, results, ) 62 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/cart.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 | {% load humanize %} 6 | 7 |
8 | 9 |

{{ shoppingcart}}

10 | 11 | {% if is_cart_empty %} 12 | 13 |

Your shopping cart is empty

14 | 15 | {% else %} 16 | 17 |
18 | 19 | {% csrf_token %} 20 | 21 | {{ form.management_form }} 22 | 23 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for item_form in form %} 39 | 40 | 41 | 44 | 45 | 46 | {% for hidden in item_form.hidden_fields %} 47 | {{ hidden }} 48 | {% endfor %} 49 | 50 | {% endfor %} 51 | 52 |
GameQuantityPrice per unitOptions
{{item_form.instance.game.name}} 42 | {{item_form.quantity}} 43 | ${{item_form.instance.price_per_unit|floatformat:2|intcomma}}{{item_form.DELETE}} Remove item
53 |
54 |
55 | 62 | {% endif %} 63 |
64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | from collections import namedtuple 4 | 5 | from pytify.auth import AuthMethod 6 | 7 | Config = namedtuple('Config', ['client_id', 8 | 'client_secret', 9 | 'access_token_url', 10 | 'auth_url', 11 | 'api_version', 12 | 'api_url', 13 | 'base_url', 14 | 'auth_method', ]) 15 | 16 | 17 | def read_config(): 18 | current_dir = os.path.abspath(os.curdir) 19 | file_path = os.path.join(current_dir, 'config.yaml') 20 | 21 | try: 22 | with open(file_path, mode='r', encoding='UTF-8') as file: 23 | config = yaml.load(file) 24 | 25 | config['base_url'] = f'{config["api_url"]}/{config["api_version"]}' 26 | 27 | auth_method = config['auth_method'] 28 | config['auth_method'] = AuthMethod.__members__.get(auth_method) 29 | 30 | return Config(**config) 31 | 32 | except IOError as e: 33 | print(""" Error: couldn''t file the configuration file `config.yaml` 34 | 'on your current directory. 35 | 36 | Default format is:', 37 | 38 | client_id: 'your_client_id' 39 | client_secret: 'you_client_secret' 40 | access_token_url: 'https://accounts.spotify.com/api/token' 41 | auth_url: 'http://accounts.spotify.com/authorize' 42 | api_version: 'v1' 43 | api_url: 'http//api.spotify.com' 44 | auth_method: 'authentication method' 45 | 46 | * auth_method can be CLIENT_CREDENTIALS or AUTHORIZATION_CODE""") 47 | raise 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | *.DS_Store 103 | *.pytest_cache 104 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/cart.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 | {% load humanize %} 6 | 7 |
8 | 9 |

{{ shoppingcart}}

10 | 11 | {% if is_cart_empty %} 12 | 13 |

Your shopping cart is empty

14 | 15 | {% else %} 16 | 17 |
18 | 19 | {% csrf_token %} 20 | 21 | {{ form.management_form }} 22 | 23 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for item_form in form %} 39 | 40 | 41 | 44 | 45 | 46 | {% for hidden in item_form.hidden_fields %} 47 | {{ hidden }} 48 | {% endfor %} 49 | 50 | {% endfor %} 51 | 52 |
GameQuantityPrice per unitOptions
{{item_form.instance.game.name}} 42 | {{item_form.quantity}} 43 | ${{item_form.instance.price_per_unit|floatformat:2|intcomma}}{{item_form.DELETE}} Remove item
53 |
54 |
55 | 65 | {% endif %} 66 |
67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

TempMessenger

8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 | 68 | 69 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/send_order.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import argparse 4 | from http import HTTPStatus 5 | 6 | import requests 7 | 8 | 9 | def setUpData(order_id): 10 | data = { 11 | "items": [ 12 | { 13 | "name": "Prod 001", 14 | "price_per_unit": 10, 15 | "product_id": 1, 16 | "quantity": 2 17 | }, 18 | { 19 | "name": "Prod 002", 20 | "price_per_unit": 12, 21 | "product_id": 2, 22 | "quantity": 2 23 | } 24 | ], 25 | "order_customer": { 26 | "customer_id": 14, 27 | "email": "test@test.com", 28 | "name": "Test User" 29 | }, 30 | "order_id": order_id, 31 | "status": 1, 32 | "total": "190.00" 33 | } 34 | 35 | return data 36 | 37 | 38 | def send_order(data): 39 | 40 | token = '' 41 | 42 | headers = { 43 | 'Authorization': f'Token {token}', 44 | 'Content-type': 'application/json' 45 | } 46 | 47 | response = requests.post( 48 | 'http://127.0.0.1:8000/api/order/add/', 49 | headers=headers, 50 | data=json.dumps(data)) 51 | 52 | if response.status_code == HTTPStatus.NO_CONTENT: 53 | print('Ops! Something went wrong!') 54 | sys.exit(1) 55 | 56 | print('Request was successfull') 57 | 58 | 59 | if __name__ == '__main__': 60 | 61 | parser = argparse.ArgumentParser( 62 | description='Create a order for test') 63 | 64 | parser.add_argument('--orderid', 65 | dest='order_id', 66 | required=True, 67 | help='Specify the the order id') 68 | 69 | args = parser.parse_args() 70 | 71 | data = setUpData(args.order_id) 72 | send_order(data) 73 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/actions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from argparse import Action 3 | from datetime import datetime 4 | 5 | from .db import DbClient 6 | from .request import fetch_exchange_rates_by_currency 7 | from currency_converter.config import get_config 8 | 9 | 10 | class SetBaseCurrency(Action): 11 | def __init__(self, option_strings, dest, args=None, **kwargs): 12 | super().__init__(option_strings, dest, **kwargs) 13 | 14 | def __call__(self, parser, namespace, value, option_string=None): 15 | self.dest = value 16 | 17 | try: 18 | with DbClient('exchange_rates', 'config') as db: 19 | db.update( 20 | {'base_currency': {'$ne': None}}, 21 | {'base_currency': value}) 22 | 23 | print(f'Base currency set to {value}') 24 | except Exception as e: 25 | print(e) 26 | finally: 27 | sys.exit(0) 28 | 29 | 30 | class UpdateForeignerExchangeRates(Action): 31 | def __init__(self, option_strings, dest, args=None, **kwargs): 32 | super().__init__(option_strings, dest, **kwargs) 33 | 34 | def __call__(self, parser, namespace, value, option_string=None): 35 | 36 | setattr(namespace, self.dest, True) 37 | 38 | try: 39 | config = get_config() 40 | base_currency = config['base_currency'] 41 | print(('Fetching exchange rates from fixer.io' 42 | f' [base currency: {base_currency}]')) 43 | response = fetch_exchange_rates_by_currency(base_currency) 44 | response['date'] = datetime.utcnow() 45 | 46 | with DbClient('exchange_rates', 'rates') as db: 47 | db.update( 48 | {'base': base_currency}, 49 | response) 50 | except Exception as e: 51 | print(e) 52 | finally: 53 | sys.exit(0) 54 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/send_order.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import argparse 4 | from http import HTTPStatus 5 | 6 | import requests 7 | 8 | 9 | def setUpData(order_id): 10 | data = { 11 | "items": [ 12 | { 13 | "name": "Prod 001", 14 | "price_per_unit": 10, 15 | "product_id": 1, 16 | "quantity": 2 17 | }, 18 | { 19 | "name": "Prod 002", 20 | "price_per_unit": 12, 21 | "product_id": 2, 22 | "quantity": 2 23 | } 24 | ], 25 | "order_customer": { 26 | "customer_id": 14, 27 | "email": "test@test.com", 28 | "name": "Test User" 29 | }, 30 | "order_id": order_id, 31 | "status": 1, 32 | "total": "190.00" 33 | } 34 | 35 | return data 36 | 37 | 38 | def send_order(data): 39 | 40 | token = '8d19ddce090211fffe22af6c06cdfd06ecb94f4e' 41 | 42 | headers = { 43 | 'Authorization': f'Token {token}', 44 | 'Content-type': 'application/json' 45 | } 46 | 47 | response = requests.post( 48 | 'http://127.0.0.1:8000/api/order/add/', 49 | headers=headers, 50 | data=json.dumps(data)) 51 | 52 | if response.status_code == HTTPStatus.NO_CONTENT: 53 | print('Ops! Something went wrong!') 54 | sys.exit(1) 55 | 56 | print('Request was successfull') 57 | 58 | 59 | if __name__ == '__main__': 60 | 61 | parser = argparse.ArgumentParser( 62 | description='Create a order for test') 63 | 64 | parser.add_argument('--orderid', 65 | dest='order_id', 66 | required=True, 67 | help='Specify the the order id') 68 | 69 | args = parser.parse_args() 70 | 71 | data = setUpData(args.order_id) 72 | send_order(data) 73 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/serializers.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | from rest_framework import serializers 4 | 5 | from .models import Order, OrderItems, OrderCustomer 6 | 7 | 8 | class OrderCustomerSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = OrderCustomer 11 | fields = ('customer_id', 'email', 'name', ) 12 | 13 | def create(self, validated_data): 14 | order_item = OrderCustomer.objects.create(**validated_data) 15 | order_item.save() 16 | return order_item 17 | 18 | 19 | class OrderItemSerializer(serializers.ModelSerializer): 20 | class Meta: 21 | model = OrderItems 22 | fields = ('name', 'price_per_unit', 'product_id', 'quantity', ) 23 | 24 | 25 | class OrderSerializer(serializers.ModelSerializer): 26 | items = OrderItemSerializer(many=True) 27 | order_customer = OrderCustomerSerializer() 28 | status = serializers.SerializerMethodField() 29 | 30 | class Meta: 31 | depth = 1 32 | model = Order 33 | fields = ('items', 'total', 'order_customer', 34 | 'created_at', 'id', 'status', ) 35 | 36 | def get_status(self, obj): 37 | return obj.get_status_display() 38 | 39 | def _create_order_item(self, item, order): 40 | item['order'] = order 41 | return OrderItems(**item) 42 | 43 | def create(self, validated_data): 44 | validated_customer = validated_data.pop('order_customer') 45 | validated_items = validated_data.pop('items') 46 | 47 | customer = OrderCustomer.objects.create(**validated_customer) 48 | 49 | validated_data['order_customer'] = customer 50 | order = Order.objects.create(**validated_data) 51 | 52 | mapped_items = map( 53 | functools.partial( 54 | self._create_order_item, order=order), validated_items 55 | ) 56 | 57 | OrderItems.objects.bulk_create(mapped_items) 58 | 59 | return order 60 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/serializers.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | from rest_framework import serializers 4 | 5 | from .models import Order, OrderItems, OrderCustomer 6 | 7 | 8 | class OrderCustomerSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = OrderCustomer 11 | fields = ('customer_id', 'email', 'name', ) 12 | 13 | def create(self, validated_data): 14 | order_item = OrderCustomer.objects.create(**validated_data) 15 | order_item.save() 16 | return order_item 17 | 18 | class OrderItemSerializer(serializers.ModelSerializer): 19 | class Meta: 20 | model = OrderItems 21 | fields = ('name', 'price_per_unit', 'product_id', 'quantity', ) 22 | 23 | 24 | class OrderSerializer(serializers.ModelSerializer): 25 | items = OrderItemSerializer(many=True) 26 | order_customer = OrderCustomerSerializer() 27 | status = serializers.SerializerMethodField() 28 | 29 | 30 | class Meta: 31 | depth = 1 32 | model = Order 33 | fields = ('items', 'total', 'order_customer', 34 | 'created_at', 'id', 'status', ) 35 | 36 | def get_status(self, obj): 37 | return obj.get_status_display() 38 | 39 | 40 | def _create_order_item(self, item, order): 41 | item['order'] = order 42 | return OrderItems(**item) 43 | 44 | def create(self, validated_data): 45 | validated_customer = validated_data.pop('order_customer') 46 | validated_items = validated_data.pop('items') 47 | 48 | customer = OrderCustomer.objects.create(**validated_customer) 49 | 50 | validated_data['order_customer'] = customer 51 | order = Order.objects.create(**validated_data) 52 | 53 | mapped_items = map( 54 | functools.partial( 55 | self._create_order_item, order=order), validated_items 56 | ) 57 | 58 | OrderItems.objects.bulk_create(mapped_items) 59 | 60 | return order 61 | 62 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

TempMessenger

8 | 9 |
10 | 11 | {% if authenticated %} 12 |
13 | 14 | 15 |
16 |

Logout

17 | {% else %} 18 |

Login

19 |

Sign up

20 | {% endif %} 21 | 22 | 76 | 77 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/dependencies/redis.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from nameko.extensions import DependencyProvider 4 | from redis import StrictRedis 5 | 6 | 7 | MESSAGE_LIFETIME = 10000 8 | 9 | 10 | class MessageStore(DependencyProvider): 11 | 12 | def setup(self): 13 | redis_url = self.container.config['REDIS_URL'] 14 | self.client = RedisClient(redis_url) 15 | 16 | def stop(self): 17 | del self.client 18 | 19 | def get_dependency(self, worker_ctx): 20 | return self.client 21 | 22 | 23 | class RedisClient: 24 | 25 | def __init__(self, url): 26 | self.redis = StrictRedis.from_url( 27 | url, decode_responses=True, charset='utf-8' 28 | ) 29 | 30 | def get_message(self, message_id): 31 | message = self.redis.get(message_id) 32 | 33 | if message is None: 34 | raise RedisError( 35 | 'Message not found: {}'.format(message_id) 36 | ) 37 | 38 | return message 39 | 40 | def save_message(self, message): 41 | message_id = uuid4().hex 42 | self.redis.set(message_id, message, px=MESSAGE_LIFETIME) 43 | 44 | return message_id 45 | 46 | # def get_all_messages(self): 47 | # message_ids = self.redis.keys() 48 | # messages = [] 49 | 50 | # for message_id in message_ids: 51 | # message = self.redis.get(message_id) 52 | # messages.append( 53 | # { 54 | # 'id': message_id, 55 | # 'message': self.redis.get(message_id), 56 | # 'expires_in': self.redis.pttl(message_id), 57 | # } 58 | # ) 59 | 60 | # return messages 61 | 62 | def get_all_messages(self): 63 | return [ 64 | { 65 | 'id': message_id, 66 | 'message': self.redis.get(message_id), 67 | 'expires_in': self.redis.pttl(message_id), 68 | } 69 | for message_id in self.redis.keys() 70 | ] 71 | 72 | 73 | class RedisError(Exception): 74 | pass 75 | -------------------------------------------------------------------------------- /Chapter03/twitter_auth.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import parse_qsl 2 | 3 | import yaml 4 | 5 | from flask import Flask 6 | from flask import render_template 7 | from flask import request 8 | 9 | import oauth2 as oauth 10 | 11 | from core import read_config 12 | from core.models import RequestToken 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | client = None 18 | consumer = None 19 | req_token = None 20 | 21 | 22 | def get_oauth_token(config): 23 | 24 | global consumer 25 | global client 26 | global req_token 27 | 28 | consumer = oauth.Consumer(config.consumer_key, config.consumer_secret) 29 | client = oauth.Client(consumer) 30 | 31 | resp, content = client.request(config.request_token_url, 'GET') 32 | 33 | if resp['status'] != '200': 34 | raise Exception("Invalid response {}".format(resp['status'])) 35 | 36 | request_token = dict(parse_qsl(content.decode('utf-8'))) 37 | 38 | req_token = RequestToken(**request_token) 39 | 40 | 41 | @app.route('/') 42 | def home(): 43 | config = read_config() 44 | 45 | get_oauth_token(config) 46 | 47 | url = f'{config.authorize_url}?oauth_token={req_token.oauth_token}' 48 | 49 | return render_template('index.html', link=url) 50 | 51 | 52 | @app.route('/callback') 53 | def callback(): 54 | 55 | global req_token 56 | global consumer 57 | 58 | config = read_config() 59 | 60 | oauth_verifier = request.args.get('oauth_verifier', '') 61 | 62 | token = oauth.Token(req_token.oauth_token, 63 | req_token.oauth_token_secret) 64 | 65 | token.set_verifier(oauth_verifier) 66 | 67 | client = oauth.Client(consumer, token) 68 | 69 | resp, content = client.request(config.access_token_url, 'POST') 70 | access_token = dict(parse_qsl(content.decode('utf-8'))) 71 | 72 | with open('.twitterauth', 'w') as req_auth: 73 | file_content = yaml.dump(access_token, default_flow_style=False) 74 | req_auth.write(file_content) 75 | 76 | return 'All set! You can close the browser window and stop the server.' 77 | 78 | 79 | if __name__ == '__main__': 80 | app.run(host='localhost', port=3000) 81 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/data_manager.py: -------------------------------------------------------------------------------- 1 | from .menu_item import MenuItem 2 | 3 | from pytify.core import search_artist 4 | from pytify.core import get_artist_albums 5 | from pytify.core import get_album_tracks 6 | from pytify.core import play 7 | 8 | from .empty_results_error import EmptyResultsError 9 | 10 | from pytify.auth import authenticate 11 | from pytify.core import read_config 12 | 13 | 14 | class DataManager(): 15 | 16 | def __init__(self): 17 | self._conf = read_config() 18 | self._auth = authenticate(self._conf) 19 | 20 | def search_artist(self, criteria): 21 | results = search_artist(criteria, self._auth) 22 | items = results['artists']['items'] 23 | 24 | if not items: 25 | raise EmptyResultsError(f'Could not find the artist: {criteria}') 26 | 27 | return items[0] 28 | 29 | def _format_artist_label(self, item): 30 | return f'{item["name"]} ({item["type"]})' 31 | 32 | def _format_track_label(self, item): 33 | time = int(item['duration_ms']) 34 | minutes = int((time / 60000) % 60) 35 | seconds = int((time / 1000) % 60) 36 | 37 | track_name = item['name'] 38 | 39 | return f'{track_name} - [{minutes}:{seconds}]' 40 | 41 | def get_artist_albums(self, artist_id, max_items=20): 42 | 43 | albums = get_artist_albums(artist_id, self._auth)['items'] 44 | 45 | if not bool(albums): 46 | raise EmptyResultsError(('Could not find any albums for' 47 | f'the artist_id: {artist_id}')) 48 | 49 | return [MenuItem(self._format_artist_label(album), album) 50 | for album in albums[:max_items]] 51 | 52 | def get_album_tracklist(self, album_id): 53 | 54 | results = get_album_tracks(album_id, self._auth) 55 | 56 | if not results: 57 | raise EmptyResultsError('Could not find the tracks for this album') 58 | 59 | tracks = results['items'] 60 | 61 | return [MenuItem(self._format_track_label(track), track) 62 | for track in tracks] 63 | 64 | def play(self, track_uri): 65 | play(track_uri, self._auth) -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/forecast.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from .forecast_type import ForecastType 3 | 4 | 5 | class Forecast: 6 | def __init__( 7 | self, 8 | current_temp, 9 | humidity, 10 | wind, 11 | high_temp=None, 12 | low_temp=None, 13 | description='', 14 | forecast_date=None, 15 | forecast_type=ForecastType.TODAY): 16 | self._current_temp = current_temp 17 | self._high_temp = high_temp 18 | self._low_temp = low_temp 19 | self._humidity = humidity 20 | self._wind = wind 21 | self._description = description 22 | self._forecast_type = forecast_type 23 | 24 | if forecast_date is None: 25 | self.forecast_date = date.today() 26 | else: 27 | self._forecast_date = forecast_date 28 | 29 | @property 30 | def forecast_date(self): 31 | return self._forecast_date 32 | 33 | @forecast_date.setter 34 | def forecast_date(self, forecast_date): 35 | self._forecast_date = forecast_date.strftime("%a %b %d") 36 | 37 | @property 38 | def current_temp(self): 39 | return self._current_temp 40 | 41 | @property 42 | def humidity(self): 43 | return self._humidity 44 | 45 | @property 46 | def wind(self): 47 | return self._wind 48 | 49 | @property 50 | def description(self): 51 | return self._description 52 | 53 | def __str__(self): 54 | temperature = None 55 | offset = ' ' * 4 56 | 57 | if self._forecast_type == ForecastType.TODAY: 58 | temperature = (f'{offset}{self._current_temp}\xb0\n' 59 | f'{offset}High {self._high_temp}\xb0 / ' 60 | f'Low {self._low_temp}\xb0 ') 61 | else: 62 | temperature = (f'{offset}High {self._high_temp}\xb0 / ' 63 | f'Low {self._low_temp}\xb0 ') 64 | 65 | return (f'>> {self.forecast_date}\n' 66 | f'{temperature}' 67 | f'({self._description})\n' 68 | f'{offset}Wind: ' 69 | f'{self._wind} / Humidity: {self._humidity}\n') 70 | -------------------------------------------------------------------------------- /Chapter06/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | 3 | import pytest 4 | from fakeredis import FakeStrictRedis 5 | from nameko.containers import ServiceContainer 6 | 7 | from temp_messenger.dependencies.users import Base 8 | from temp_messenger.message_service import MessageService 9 | from temp_messenger.user_service import UserService 10 | 11 | 12 | TEST_DB_URI = ( 13 | 'postgresql+psycopg2://postgres:secret@localhost:' 14 | '5433/users?client_encoding=utf8' 15 | ) 16 | 17 | 18 | @pytest.fixture() 19 | def hashed_password(): 20 | with patch( 21 | 'temp_messenger.dependencies.users.bcrypt.hashpw' 22 | ) as hashpw: 23 | hashpw.return_value = b'hashed_password' 24 | yield hashpw 25 | 26 | 27 | @pytest.fixture 28 | def test_config(web_config): 29 | config = { 30 | 'AMQP_URI': 'pyamqp://guest:guest@localhost', 31 | 'REDIS_URL': 'redis://redis:6379/0', 32 | 'DB_URIS': { 33 | "user_service:Base": TEST_DB_URI 34 | } 35 | } 36 | config.update(**web_config) 37 | return config 38 | 39 | 40 | @pytest.fixture 41 | def fake_strict_redis(): 42 | with patch( 43 | 'temp_messenger.dependencies.messages.StrictRedis', 44 | FakeStrictRedis 45 | ) as fake_strict_redis: 46 | yield fake_strict_redis 47 | fake_strict_redis().flushall() 48 | 49 | 50 | @pytest.fixture 51 | def message_svc(test_config, fake_strict_redis): 52 | message_svc = ServiceContainer(MessageService, test_config) 53 | message_svc.start() 54 | 55 | return message_svc 56 | 57 | 58 | @pytest.fixture 59 | def user_svc(test_config): 60 | user_service = ServiceContainer(UserService, test_config) 61 | user_service.start() 62 | 63 | return user_service 64 | 65 | 66 | @pytest.fixture(scope='session') 67 | def db_url(): 68 | return TEST_DB_URI 69 | 70 | 71 | @pytest.fixture(scope='session') 72 | def model_base(): 73 | return Base 74 | 75 | 76 | @pytest.fixture(scope='session') 77 | def db_engine_options(): 78 | return dict( 79 | client_encoding='utf8', 80 | connect_args={'client_encoding': 'utf8'} 81 | ) 82 | 83 | 84 | @pytest.fixture 85 | def uuid4(): 86 | with patch( 87 | 'temp_messenger.dependencies.messages.uuid4' 88 | ) as uuid4: 89 | yield uuid4 90 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/dependencies/messages.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from nameko.extensions import DependencyProvider 4 | from redis import StrictRedis 5 | 6 | 7 | MESSAGE_LIFETIME = 10000 8 | 9 | 10 | class MessageError(Exception): 11 | pass 12 | 13 | 14 | class MessageStore(DependencyProvider): 15 | 16 | def setup(self): 17 | self.redis_url = self.container.config['REDIS_URL'] 18 | self.client = RedisClient(self.redis_url) 19 | 20 | def stop(self): 21 | del self.client 22 | 23 | def get_dependency(self, worker_ctx): 24 | return self.client 25 | 26 | 27 | class RedisClient: 28 | 29 | def __init__(self, url): 30 | self.redis = StrictRedis.from_url( 31 | url, decode_responses=True, charset='utf-8' 32 | ) 33 | 34 | def get_message(self, message_id): 35 | message = self.redis.hget(message_id, 'message') 36 | 37 | if message is None: 38 | raise MessageError( 39 | 'Message not found: {}'.format(message_id) 40 | ) 41 | 42 | return message 43 | 44 | def save_message(self, email, message): 45 | message_id = uuid4().hex 46 | payload = { 47 | 'email': email, 48 | 'message': message, 49 | } 50 | self.redis.hmset(message_id, payload) 51 | self.redis.pexpire(message_id, MESSAGE_LIFETIME) 52 | 53 | return message_id 54 | 55 | # def get_all_messages(self): 56 | # message_ids = self.redis.keys() 57 | # messages = [] 58 | 59 | # for message_id in message_ids: 60 | # message = self.redis.get(message_id) 61 | # messages.append( 62 | # { 63 | # 'id': message_id, 64 | # 'message': self.redis.get(message_id), 65 | # 'expires_in': self.redis.pttl(message_id), 66 | # } 67 | # ) 68 | 69 | # return messages 70 | 71 | def get_all_messages(self): 72 | return [ 73 | { 74 | 'id': message_id, 75 | 'email': self.redis.hget(message_id, 'email'), 76 | 'message': self.redis.hget(message_id, 'message'), 77 | 'expires_in': self.redis.pttl(message_id), 78 | } 79 | for message_id in self.redis.keys() 80 | ] 81 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-02-15 23:51 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Order', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('total', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('last_updated', models.DateTimeField(auto_now=True)), 22 | ('status', models.IntegerField(choices=[(1, 'Received'), (2, 'Processing'), (3, 'Payment complete'), (4, 'Shipping'), (5, 'Completed'), (6, 'Cancelled')], default='1')), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='OrderCustomer', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('customer_id', models.IntegerField()), 30 | ('name', models.CharField(max_length=100)), 31 | ('email', models.CharField(max_length=100)), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='OrderItems', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('product_id', models.IntegerField()), 39 | ('name', models.CharField(max_length=200)), 40 | ('quantity', models.IntegerField()), 41 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 42 | ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='main.Order')), 43 | ], 44 | options={ 45 | 'verbose_name_plural': 'Order items', 46 | }, 47 | ), 48 | migrations.AddField( 49 | model_name='order', 50 | name='order_customer', 51 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.OrderCustomer'), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-02-15 23:51 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Order', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('total', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('last_updated', models.DateTimeField(auto_now=True)), 22 | ('status', models.IntegerField(choices=[(1, 'Received'), (2, 'Processing'), (3, 'Payment complete'), (4, 'Shipping'), (5, 'Completed'), (6, 'Cancelled')], default='1')), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='OrderCustomer', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('customer_id', models.IntegerField()), 30 | ('name', models.CharField(max_length=100)), 31 | ('email', models.CharField(max_length=100)), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='OrderItems', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('product_id', models.IntegerField()), 39 | ('name', models.CharField(max_length=200)), 40 | ('quantity', models.IntegerField()), 41 | ('price_per_unit', models.DecimalField(decimal_places=2, default=0, max_digits=9)), 42 | ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='main.Order')), 43 | ], 44 | options={ 45 | 'verbose_name_plural': 'Order items', 46 | }, 47 | ), 48 | migrations.AddField( 49 | model_name='order', 50 | name='order_customer', 51 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.OrderCustomer'), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/spotify_auth.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlencode 2 | 3 | import requests 4 | import json 5 | 6 | from flask import Flask 7 | from flask import render_template 8 | from flask import request 9 | 10 | from pytify.core import read_config 11 | from pytify.core import BadRequestError 12 | from pytify.auth import Authorization 13 | from pytify.auth import get_auth_key 14 | 15 | 16 | app = Flask(__name__) 17 | 18 | 19 | @app.route("/") 20 | def home(): 21 | config = read_config() 22 | 23 | params = { 24 | 'client_id': config.client_id, 25 | 'response_type': 'code', 26 | 'redirect_uri': 'http://localhost:3000/callback', 27 | 'scope': 'user-read-private user-modify-playback-state', 28 | } 29 | 30 | enc_params = urlencode(params) 31 | url = f'{config.auth_url}?{enc_params}' 32 | 33 | return render_template('index.html', link=url) 34 | 35 | 36 | @app.route('/callback') 37 | def callback(): 38 | 39 | code = request.args.get('code', '') 40 | response = _authorization_code_request(code) 41 | 42 | with open('.pytify', mode='w', encoding='utf-8') as file: 43 | file.write(response.refresh_token) 44 | 45 | return 'All set! You can close the browser window and stop the server.' 46 | 47 | 48 | def _authorization_code_request(auth_code): 49 | 50 | config = read_config() 51 | 52 | auth_key = get_auth_key(config.client_id, config.client_secret) 53 | 54 | headers = {'Authorization': f'Basic {auth_key}', } 55 | 56 | options = { 57 | 'code': auth_code, 58 | 'redirect_uri': 'http://localhost:3000/callback', 59 | 'grant_type': 'authorization_code', 60 | 'json': True 61 | } 62 | 63 | response = requests.post( 64 | config.access_token_url, 65 | headers=headers, 66 | data=options 67 | ) 68 | 69 | content = json.loads(response.content.decode('utf-8')) 70 | 71 | if not response.ok: 72 | error_description = content.get('error_description') 73 | raise BadRequestError(error_description) 74 | 75 | access_token = content.get('access_token', None) 76 | token_type = content.get('token_type', None) 77 | expires_in = content.get('expires_in', None) 78 | refresh_token = content.get('refresh_token', None) 79 | scope = content.get('scope', None) 80 | 81 | return Authorization(access_token, token_type, expires_in, scope, refresh_token) 82 | 83 | 84 | if __name__ == '__main__': 85 | app.run(host='localhost', port=3000) -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/menu.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import curses.panel 3 | 4 | from .alignment import Alignment 5 | from .panel import Panel 6 | 7 | NEW_LINE = 10 8 | CARRIAGE_RETURN = 13 9 | 10 | 11 | class Menu(Panel): 12 | 13 | def __init__(self, title, dimensions, align=Alignment.LEFT, items=[]): 14 | super().__init__(title, dimensions) 15 | self._align = align 16 | self.items = items 17 | 18 | def get_selected(self): 19 | items = [x for x in self.items if x.selected] 20 | return None if not items else items[0] 21 | 22 | def _select(self, expr): 23 | current = self.get_selected() 24 | index = self.items.index(current) 25 | new_index = expr(index) 26 | 27 | if new_index < 0: 28 | return 29 | 30 | if new_index > index and new_index >= len(self.items): 31 | return 32 | 33 | self.items[index].selected = False 34 | self.items[new_index].selected = True 35 | 36 | def next(self): 37 | self._select(lambda index: index + 1) 38 | 39 | def previous(self): 40 | self._select(lambda index: index - 1) 41 | 42 | def _initialize_items(self): 43 | longest_label_item = max(self.items, key=len) 44 | 45 | for item in self.items: 46 | if item != longest_label_item: 47 | padding = (len(longest_label_item) - len(item)) * ' ' 48 | item.label = (f'{item}{padding}' 49 | if self._align == Alignment.LEFT 50 | else f'{padding}{item}') 51 | 52 | if not self.get_selected(): 53 | self.items[0].selected = True 54 | 55 | def init(self): 56 | self._initialize_items() 57 | 58 | def handle_events(self, key): 59 | if key == curses.KEY_UP: 60 | self.previous() 61 | elif key == curses.KEY_DOWN: 62 | self.next() 63 | elif key == curses.KEY_ENTER or key == NEW_LINE or key == CARRIAGE_RETURN: 64 | selected_item = self.get_selected() 65 | return selected_item.action 66 | 67 | def __iter__(self): 68 | return iter(self.items) 69 | 70 | def update(self): 71 | pos_x = 2 72 | pos_y = 2 73 | 74 | for item in self.items: 75 | self._win.addstr( 76 | pos_y, 77 | pos_x, 78 | item.label, 79 | curses.A_REVERSE if item.selected else curses.A_NORMAL) 80 | pos_y += 1 81 | 82 | self._win.refresh() 83 | -------------------------------------------------------------------------------- /Chapter03/app.py: -------------------------------------------------------------------------------- 1 | from core import parse_commandline_args 2 | from core import execute_request 3 | from core import Runner 4 | 5 | from core.twitter import HashtagStatsManager 6 | 7 | from tkinter import Tk 8 | from tkinter import Frame 9 | from tkinter import Label 10 | from tkinter import StringVar 11 | from tkinter.ttk import Button 12 | 13 | 14 | class Application(Frame): 15 | 16 | def __init__(self, hashtags=[], master=None): 17 | super().__init__(master) 18 | 19 | self._manager = HashtagStatsManager(hashtags) 20 | 21 | self._runner = Runner(self._on_success, 22 | self._on_error, 23 | self._on_complete) 24 | 25 | self._items = {hashtag: StringVar() for hashtag in hashtags} 26 | self.set_header() 27 | self.create_labels() 28 | self.pack() 29 | 30 | self.button = Button(self, style='start.TButton', text='Update', 31 | command=self._fetch_data) 32 | self.button.pack(side="bottom") 33 | 34 | def set_header(self): 35 | title = Label(self, 36 | text='Voting for hasthags', 37 | font=("Helvetica", 24), 38 | height=4) 39 | title.pack() 40 | 41 | def create_labels(self): 42 | for key, value in self._items.items(): 43 | label = Label(self, 44 | textvariable=value, 45 | font=("Helvetica", 20), height=3) 46 | label.pack() 47 | self._items[key].set(f'#{key}\nNumber of votes: 0') 48 | 49 | def _update_label(self, data): 50 | hashtag, result = data 51 | 52 | total = self._manager.hashtags.get(hashtag.name).total 53 | 54 | self._items[hashtag.name].set( 55 | f'#{hashtag.name}\nNumber of votes: {total}') 56 | 57 | def _fetch_data(self): 58 | self._runner.exec(execute_request, 59 | self._manager.hashtags) 60 | 61 | def _on_error(self, error_message): 62 | raise Exception(error_message) 63 | 64 | def _on_success(self, data): 65 | hashtag, _ = data 66 | self._manager.update(data) 67 | self._update_label(data) 68 | 69 | def _on_complete(self): 70 | pass 71 | 72 | 73 | def start_app(args): 74 | root = Tk() 75 | 76 | app = Application(hashtags=args.hashtags, master=root) 77 | app.master.title("Twitter votes") 78 | app.master.geometry("400x700+100+100") 79 | app.mainloop() 80 | 81 | 82 | def main(): 83 | args = parse_commandline_args() 84 | start_app(args) 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/service.py: -------------------------------------------------------------------------------- 1 | import json 2 | from operator import itemgetter 3 | 4 | from nameko.rpc import rpc, RpcProxy 5 | from nameko.web.handlers import http 6 | from werkzeug.wrappers import Response 7 | 8 | from .dependencies.redis import MessageStore 9 | from .dependencies.jinja2 import Jinja2 10 | 11 | 12 | class MessageService: 13 | 14 | name = 'message_service' 15 | 16 | message_store = MessageStore() 17 | 18 | @rpc 19 | def get_message(self, message_id): 20 | return self.message_store.get_message(message_id) 21 | 22 | @rpc 23 | def save_message(self, message): 24 | message_id = self.message_store.save_message(message) 25 | return message_id 26 | 27 | @rpc 28 | def get_all_messages(self): 29 | messages = self.message_store.get_all_messages() 30 | sorted_messages = sort_messages_by_expiry(messages) 31 | return sorted_messages 32 | 33 | 34 | class WebServer: 35 | 36 | name = 'web_server' 37 | message_service = RpcProxy('message_service') 38 | jinja = Jinja2() 39 | 40 | @http('GET', '/') 41 | def home(self, request): 42 | messages = self.message_service.get_all_messages() 43 | rendered_template = self.jinja.render_home(messages) 44 | html_response = create_html_response(rendered_template) 45 | 46 | return html_response 47 | 48 | @http('POST', '/messages') 49 | def post_message(self, request): 50 | try: 51 | data = get_request_data(request) 52 | except json.JSONDecodeError: 53 | return 400, 'JSON payload expected' 54 | 55 | try: 56 | message = data['message'] 57 | except KeyError: 58 | return 400, 'No message given' 59 | 60 | self.message_service.save_message(message) 61 | 62 | return 204, '' 63 | 64 | @http('GET', '/messages') 65 | def get_messages(self, request): 66 | messages = self.message_service.get_all_messages() 67 | return create_json_response(messages) 68 | 69 | 70 | def create_html_response(content): 71 | headers = {'Content-Type': 'text/html'} 72 | return Response(content, status=200, headers=headers) 73 | 74 | 75 | def create_json_response(content): 76 | headers = {'Content-Type': 'application/json'} 77 | json_data = json.dumps(content) 78 | return Response(json_data, status=200, headers=headers) 79 | 80 | 81 | def get_request_data(request): 82 | data_as_text = request.get_data(as_text=True) 83 | return json.loads(data_as_text) 84 | 85 | 86 | def sort_messages_by_expiry(messages, reverse=False): 87 | return sorted( 88 | messages, 89 | key=itemgetter('expires_in'), 90 | reverse=reverse 91 | ) 92 | -------------------------------------------------------------------------------- /Chapter07/gamestore/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | Vintage video games store 13 | 14 | {% load staticfiles %} 15 | 16 | 18 | 20 | 21 | 22 | 23 | 24 | 58 | 59 |
60 | 61 |
62 | {% if messages %} 63 | {% for message in messages %} 64 | {% if message.tags == 'error' %} 65 | 77 |
78 | 79 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/cmdline_parser.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from argparse import ArgumentParser 3 | 4 | from .actions import UpdateForeignerExchangeRates 5 | from .actions import SetBaseCurrency 6 | from .currency import Currency 7 | 8 | 9 | def validate_args(args): 10 | 11 | fields = [arg for arg in vars(args).items() if arg] 12 | 13 | if not fields: 14 | return False 15 | 16 | if args.value and not args.dest_currency: 17 | return False 18 | elif args.dest_currency and not args.value: 19 | return False 20 | 21 | return True 22 | 23 | 24 | def parse_commandline_args(): 25 | 26 | currency_options = [currency.name for currency in Currency] 27 | 28 | argparser = ArgumentParser( 29 | prog='currency_converter', 30 | description=('Tool that shows exchange rated and perform ' 31 | 'currency convertion, using http://fixer.io data.')) 32 | 33 | argparser.add_argument('--setbasecurrency', 34 | type=str, 35 | dest='base_currency', 36 | choices=currency_options, 37 | action=SetBaseCurrency, 38 | help='Sets the base currency to be used.') 39 | 40 | argparser.add_argument('--update', 41 | metavar='', 42 | dest='update', 43 | nargs=0, 44 | action=UpdateForeignerExchangeRates, 45 | help=('Update the foreigner exchange rates ' 46 | 'using as a reference the base currency')) 47 | 48 | argparser.add_argument('--basecurrency', 49 | type=str, 50 | dest='from_currency', 51 | choices=currency_options, 52 | help=('The base currency. If specified it will ' 53 | 'override the default currency set by' 54 | 'the --setbasecurrency option')) 55 | 56 | argparser.add_argument('--value', 57 | type=float, 58 | dest='value', 59 | help='The value to be converted') 60 | 61 | argparser.add_argument('--to', 62 | type=str, 63 | dest='dest_currency', 64 | choices=currency_options, 65 | help=('Specify the currency that the value will ' 66 | 'be converted to.')) 67 | 68 | args = argparser.parse_args() 69 | 70 | if not validate_args(args): 71 | argparser.print_help() 72 | sys.exit() 73 | 74 | return args 75 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.shortcuts import get_object_or_404 3 | 4 | from rest_framework import generics, status 5 | from rest_framework.response import Response 6 | 7 | 8 | from .models import Order 9 | from .status import Status 10 | from .view_helper import OrderListAPIBaseView 11 | from .view_helper import set_status_handler 12 | from .serializers import OrderSerializer 13 | 14 | 15 | class OrdersByCustomerView(OrderListAPIBaseView): 16 | lookup_field = 'customer_id' 17 | 18 | def get_queryset(self, customer_id): 19 | return Order.objects.get_all_orders_by_customer(customer_id) 20 | 21 | 22 | class IncompleteOrdersByCustomerView(OrderListAPIBaseView): 23 | lookup_field = 'customer_id' 24 | 25 | def get_queryset(self, customer_id): 26 | return Order.objects.get_customer_incomplete_orders( 27 | customer_id 28 | ) 29 | 30 | 31 | class CompletedOrdersByCustomerView(OrderListAPIBaseView): 32 | lookup_field = 'customer_id' 33 | 34 | def get_queryset(self, customer_id): 35 | return Order.objects.get_customer_completed_orders( 36 | customer_id 37 | ) 38 | 39 | 40 | class OrderByStatusView(OrderListAPIBaseView): 41 | lookup_field = 'status_id' 42 | 43 | def get_queryset(self, status_id): 44 | return Order.objects.get_orders_by_status( 45 | Status(status_id) 46 | ) 47 | 48 | 49 | class CreateOrderView(generics.CreateAPIView): 50 | 51 | def post(self, request, *arg, **args): 52 | serializer = OrderSerializer(data=request.data) 53 | 54 | if serializer.is_valid(): 55 | order = serializer.save() 56 | return Response( 57 | {'order_id': order.id}, 58 | status=status.HTTP_201_CREATED) 59 | 60 | return Response(status=status.HTTP_400_BAD_REQUEST) 61 | 62 | 63 | def cancel_order(request, order_id): 64 | order = get_object_or_404(Order, order_id=order_id) 65 | 66 | return set_status_handler( 67 | lambda: Order.objects.cancel_order(order) 68 | ) 69 | 70 | 71 | def set_next_status(request, order_id): 72 | order = get_object_or_404(Order, order_id=order_id) 73 | 74 | return set_status_handler( 75 | lambda: Order.objects.set_next_status(order) 76 | ) 77 | 78 | 79 | def set_status(request, order_id, status_id): 80 | order = get_object_or_404(Order, order_id=order_id) 81 | 82 | try: 83 | status = Status(status_id) 84 | except ValueError: 85 | return HttpResponse( 86 | 'The status value is invalid.', 87 | status=status.HTTP_400_BAD_REQUEST) 88 | 89 | return set_status_handler( 90 | lambda: Order.objects.set_status(order, status) 91 | ) 92 | -------------------------------------------------------------------------------- /Chapter08/gamestore/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | Vintage video games store 13 | 14 | {% load staticfiles %} 15 | 16 | 18 | 20 | 21 | 22 | 23 | 24 | 63 | 64 |
65 | 66 |
67 | {% if messages %} 68 | {% for message in messages %} 69 | {% if message.tags == 'error' %} 70 | 82 |
83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Python Programming Blueprints 5 | This is the code repository for [Python Programming Blueprints](https://www.packtpub.com/application-development/python-programming-blueprints?utm_source=github&utm_medium=repository&utm_campaign=9781786468161), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. 6 | 7 | ## About the Book 8 | Python is a very powerful, high-level, object-oriented programming language. It has swiftly developed over the years to become the language of choice for software developers due to its simplicity. Python Programming Blueprints will help you build useful, real-world applications using Python. 9 | 10 | ## Instructions and Navigation 11 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 12 | 13 | All chapters have code files in their respective folders 14 | 15 | The code will look like the following: 16 | 17 | ```python 18 | def set_header(self): 19 | title = Label( 20 | self, 21 | text='Voting for hasthags', 22 | font=("Helvetica", 24), 23 | height=4 24 | ) 25 | title.pack() 26 | ``` 27 | 28 | In order to execute the code from this book on your local machine, you will need the following: 29 | 30 | - An internet connection 31 | - Virtualenv 32 | - Python 3.6 33 | - MongoDB 3.2.11 34 | - pgAdmin (refer to the official documentation at http://url.marcuspen.com/pgadmin for installation instructions) 35 | - Docker (refer to the official documentation at http://url.marcuspen.com/docker-install for installation instructions) 36 | - All other requirements will be installed as we progress through the chapters. 37 | - All instructions in this chapter are tailored toward macOS or Debian/Ubuntu systems; however, the authors have taken care to only use cross-platform dependencies. 38 | 39 | ## Related Products 40 | - [Python Microservices Development](https://www.packtpub.com/web-development/python-microservices-development?utm_source=github&utm_medium=repository&utm_campaign=9781785881114) 41 | - [Cloud Native Python](https://www.packtpub.com/application-development/cloud-native-python?utm_source=github&utm_medium=repository&utm_campaign=9781787129313) 42 | - [Python Programming with Raspberry Pi](https://www.packtpub.com/hardware-and-creative/python-programming-raspberry-pi?utm_source=github&utm_medium=repository&utm_campaign=9781786467577) 43 | 44 | ### Suggestions and Feedback 45 | Please raise a Github issue if there are any issues with the code in this repository. 46 | ### Download a free PDF 47 | 48 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
49 |

https://packt.link/free-ebook/9781786468161

-------------------------------------------------------------------------------- /Chapter01/weatherterm/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from argparse import ArgumentParser 3 | from weatherterm.core import parser_loader 4 | from weatherterm.core import ForecastType 5 | from weatherterm.core import Unit 6 | 7 | from weatherterm.core import SetUnitAction 8 | 9 | 10 | def _validate_forecast_args(args): 11 | if args.forecast_option is None: 12 | err_msg = ('One of these arguments must be used: ' 13 | '-td/--today, -5d/--fivedays, -10d/--tendays, -w/--weekend') 14 | 15 | print(f'{argparser.prog}: error: {err_msg}', file=sys.stderr) 16 | sys.exit() 17 | 18 | parsers = parser_loader.load('./weatherterm/parsers') 19 | 20 | argparser = ArgumentParser( 21 | prog='weatherterm', 22 | description='Weather info from weather.com on your terminal') 23 | 24 | required = argparser.add_argument_group('require arguments') 25 | required.add_argument('-p', '--parser', 26 | choices=parsers.keys(), 27 | required=True, 28 | dest='parser', 29 | help=('Specify which parser is going to be used to ' 30 | 'scrape weather information.')) 31 | 32 | unit_values = [name.title() for name, value in Unit.__members__.items()] 33 | 34 | argparser.add_argument('-u', '--unit', 35 | choices=unit_values, 36 | required=False, 37 | action=SetUnitAction, 38 | dest='unit', 39 | help=('Specify the unit that will be used to display ' 40 | 'the temperatures.')) 41 | 42 | required.add_argument('-a', '--areacode', 43 | required=True, 44 | dest='area_code', 45 | help=('The code area to get the weather broadcast from .' 46 | 'It can be obtained at https: // weather.com')) 47 | 48 | argparser.add_argument('-v', '--version', 49 | action='version', 50 | version='%(prog)s 1.0') 51 | 52 | argparser.add_argument('-td', '--today', 53 | dest='forecast_option', 54 | action='store_const', 55 | const=ForecastType.TODAY, 56 | help='Show the weather forecast for the current day') 57 | 58 | argparser.add_argument('-5d', '--fivedays', 59 | dest='forecast_option', 60 | action='store_const', 61 | const=ForecastType.FIVEDAYS, 62 | help='Shows the weather forecast for the next 5 days') 63 | 64 | argparser.add_argument('-10d', '--tendays', 65 | dest='forecast_option', 66 | action='store_const', 67 | const=ForecastType.TENDAYS, 68 | help='Shows the weather forecast for the next 10 days') 69 | 70 | argparser.add_argument('-w', '--weekend', 71 | dest='forecast_option', 72 | action='store_const', 73 | const=ForecastType.WEEKEND, 74 | help=('Shows the weather forecast for the next or ' 75 | 'current weekend')) 76 | 77 | args = argparser.parse_args() 78 | _validate_forecast_args(args) 79 | 80 | cls = parsers[args.parser] 81 | parser = cls() 82 | results = parser.run(args) 83 | 84 | for result in results: 85 | print(result) -------------------------------------------------------------------------------- /Chapter06/temp_messenger/dependencies/users.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | 3 | from nameko_sqlalchemy import DatabaseSession 4 | from sqlalchemy import Column, Integer, LargeBinary, Unicode 5 | from sqlalchemy.ext.declarative import declarative_base 6 | from sqlalchemy.orm.exc import NoResultFound 7 | from sqlalchemy.exc import IntegrityError 8 | 9 | 10 | HASH_WORK_FACTOR = 15 11 | Base = declarative_base() 12 | 13 | 14 | class CreateUserError(Exception): 15 | pass 16 | 17 | 18 | class UserAlreadyExists(CreateUserError): 19 | pass 20 | 21 | 22 | class UserNotFound(Exception): 23 | pass 24 | 25 | 26 | class AuthenticationError(Exception): 27 | pass 28 | 29 | 30 | class User(Base): 31 | __tablename__ = 'users' 32 | 33 | id = Column(Integer, primary_key=True) 34 | first_name = Column(Unicode(length=128)) 35 | last_name = Column(Unicode(length=128)) 36 | email = Column(Unicode(length=256), unique=True) 37 | password = Column(LargeBinary()) 38 | 39 | 40 | class UserWrapper: 41 | 42 | def __init__(self, session): 43 | self.session = session 44 | 45 | def create(self, **kwargs): 46 | plain_text_password = kwargs['password'] 47 | hashed_password = hash_password(plain_text_password) 48 | kwargs.update(password=hashed_password) 49 | 50 | user = User(**kwargs) 51 | self.session.add(user) 52 | 53 | try: 54 | self.session.commit() 55 | except IntegrityError as err: 56 | self.session.rollback() 57 | error_message = err.args[0] 58 | 59 | if 'already exists' in error_message: 60 | email = kwargs['email'] 61 | message = 'User already exists - {}'.format(email) 62 | raise UserAlreadyExists(message) 63 | else: 64 | raise CreateUserError(error_message) 65 | 66 | def get(self, email): 67 | query = self.session.query(User) 68 | 69 | try: 70 | user = query.filter_by(email=email).one() 71 | except NoResultFound: 72 | message = 'User not found - {}'.format(email) 73 | raise UserNotFound(message) 74 | 75 | return user 76 | 77 | def authenticate(self, email, password): 78 | user = self.get(email) 79 | 80 | if not bcrypt.checkpw(password.encode(), user.password): 81 | message = 'Incorrect password for {}'.format(email) 82 | raise AuthenticationError(message) 83 | 84 | return user 85 | 86 | 87 | class UserStore(DatabaseSession): 88 | 89 | def __init__(self): 90 | super().__init__(Base) 91 | 92 | def get_dependency(self, worker_ctx): 93 | database_session = super().get_dependency(worker_ctx) 94 | return UserWrapper(session=database_session) 95 | 96 | 97 | def hash_password(plain_text_password): 98 | salt = bcrypt.gensalt(rounds=HASH_WORK_FACTOR) 99 | encoded_password = plain_text_password.encode() 100 | 101 | return bcrypt.hashpw(encoded_password, salt) 102 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/managers.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from django.db.models import Manager, Q 3 | 4 | from .status import Status 5 | 6 | from .exceptions import InvalidArgumentError 7 | from .exceptions import OrderAlreadyCompletedError 8 | from .exceptions import OrderCancellationError 9 | 10 | from . import models 11 | 12 | 13 | class OrderManager(Manager): 14 | 15 | def set_status(self, order, status): 16 | if status is None or not isinstance(status, Status): 17 | raise InvalidArgumentError('status') 18 | 19 | if order is None or not isinstance(order, models.Order): 20 | raise InvalidArgumentError('order') 21 | 22 | if order.status is Status.Completed.value: 23 | raise OrderAlreadyCompletedError() 24 | 25 | order.status = status.value 26 | order.save() 27 | 28 | def cancel_order(self, order): 29 | if order is None or not isinstance(order, models.Order): 30 | raise InvalidArgumentError('order') 31 | 32 | if order.status != Status.Received.value: 33 | raise OrderCancellationError() 34 | 35 | self.set_status(order, Status.Cancelled) 36 | 37 | def get_all_orders_by_customer(self, customer_id): 38 | try: 39 | return self.filter( 40 | order_customer_id=customer_id).order_by( 41 | 'status', '-created_at') 42 | except ValueError: 43 | raise InvalidArgumentError('customer_id') 44 | 45 | def get_customer_incomplete_orders(self, customer_id): 46 | try: 47 | return self.filter( 48 | ~Q(status=Status.Completed.value), 49 | order_customer_id=customer_id).order_by('status') 50 | except ValueError: 51 | raise InvalidArgumentError('customer_id') 52 | 53 | def get_customer_completed_orders(self, customer_id): 54 | try: 55 | return self.filter( 56 | status=Status.Completed.value, 57 | order_customer_id=customer_id) 58 | except ValueError: 59 | raise InvalidArgumentError('customer_id') 60 | 61 | def get_orders_by_status(self, status): 62 | if status is None or not isinstance(status, Status): 63 | raise InvalidArgumentError('status') 64 | 65 | return self.filter(status=status.value) 66 | 67 | def get_orders_by_period(self, start_date, end_date): 68 | if start_date is None or not isinstance(start_date, datetime): 69 | raise InvalidArgumentError('start_date') 70 | 71 | if end_date is None or not isinstance(end_date, datetime): 72 | raise InvalidArgumentError('end_date') 73 | 74 | result = self.filter(created_at__range=[start_date, end_date]) 75 | return result 76 | 77 | def set_next_status(self, order): 78 | if order is None or not isinstance(order, models.Order): 79 | raise InvalidArgumentError('order') 80 | 81 | if order.status is Status.Completed.value: 82 | raise OrderAlreadyCompletedError() 83 | 84 | order.status += 1 85 | order.save() 86 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/managers.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from django.db.models import Manager, Q 3 | 4 | from .status import Status 5 | 6 | from .exceptions import InvalidArgumentError 7 | from .exceptions import OrderAlreadyCompletedError 8 | from .exceptions import OrderCancellationError 9 | 10 | from . import models 11 | 12 | class OrderManager(Manager): 13 | 14 | def set_status(self, order, status): 15 | if status is None or not isinstance(status, Status): 16 | raise InvalidArgumentError('status') 17 | 18 | if order is None or not isinstance(order, models.Order): 19 | raise InvalidArgumentError('order') 20 | 21 | if order.status is Status.Completed.value: 22 | raise OrderAlreadyCompletedError() 23 | 24 | order.status = status.value 25 | order.save() 26 | 27 | def cancel_order(self, order): 28 | if order is None or not isinstance(order, models.Order): 29 | raise InvalidArgumentError('order') 30 | 31 | if order.status != Status.Received.value: 32 | raise OrderCancellationError() 33 | 34 | self.set_status(order, Status.Cancelled) 35 | 36 | def get_all_orders_by_customer(self, customer_id): 37 | try: 38 | return self.filter( 39 | order_customer_id=customer_id).order_by( 40 | 'status', '-created_at') 41 | except ValueError: 42 | raise InvalidArgumentError('customer_id') 43 | 44 | def get_customer_incomplete_orders(self, customer_id): 45 | try: 46 | return self.filter( 47 | ~Q(status=Status.Completed.value), 48 | order_customer_id=customer_id).order_by('status') 49 | except ValueError: 50 | raise InvalidArgumentError('customer_id') 51 | 52 | def get_customer_completed_orders(self, customer_id): 53 | try: 54 | return self.filter( 55 | status=Status.Completed.value, 56 | order_customer_id=customer_id) 57 | except ValueError: 58 | raise InvalidArgumentError('customer_id') 59 | 60 | def get_orders_by_status(self, status): 61 | if status is None or not isinstance(status, Status): 62 | raise InvalidArgumentError('status') 63 | 64 | return self.filter(status=status.value) 65 | 66 | def get_orders_by_period(self, start_date, end_date): 67 | if start_date is None or not isinstance(start_date, datetime): 68 | raise InvalidArgumentError('start_date') 69 | 70 | if end_date is None or not isinstance(end_date, datetime): 71 | raise InvalidArgumentError('end_date') 72 | 73 | result = self.filter(created_at__range=[start_date, end_date]) 74 | return result 75 | 76 | def set_next_status(self, order): 77 | if order is None or not isinstance(order, models.Order): 78 | raise InvalidArgumentError('order') 79 | 80 | if order.status is Status.Completed.value: 81 | raise OrderAlreadyCompletedError() 82 | 83 | order.status += 1 84 | order.save() 85 | 86 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | from django.http import HttpResponse 4 | from django.shortcuts import get_object_or_404 5 | 6 | from rest_framework import generics, status 7 | from rest_framework.response import Response 8 | 9 | 10 | from .models import Order 11 | from .status import Status 12 | from .view_helper import OrderListAPIBaseView 13 | from .view_helper import set_status_handler 14 | from .serializers import OrderSerializer 15 | 16 | from .notifier import notify 17 | from .notification_type import NotificationType 18 | 19 | 20 | class OrdersByCustomerView(OrderListAPIBaseView): 21 | lookup_field = 'customer_id' 22 | 23 | def get_queryset(self, customer_id): 24 | return Order.objects.get_all_orders_by_customer(customer_id) 25 | 26 | 27 | class IncompleteOrdersByCustomerView(OrderListAPIBaseView): 28 | lookup_field = 'customer_id' 29 | 30 | def get_queryset(self, customer_id): 31 | return Order.objects.get_customer_incomplete_orders( 32 | customer_id 33 | ) 34 | 35 | 36 | class CompletedOrdersByCustomerView(OrderListAPIBaseView): 37 | lookup_field = 'customer_id' 38 | 39 | def get_queryset(self, customer_id): 40 | return Order.objects.get_customer_completed_orders( 41 | customer_id 42 | ) 43 | 44 | 45 | class OrderByStatusView(OrderListAPIBaseView): 46 | lookup_field = 'status_id' 47 | 48 | def get_queryset(self, status_id): 49 | return Order.objects.get_orders_by_status( 50 | Status(status_id) 51 | ) 52 | 53 | 54 | class CreateOrderView(generics.CreateAPIView): 55 | 56 | def post(self, request, *arg, **args): 57 | serializer = OrderSerializer(data=request.data) 58 | 59 | if serializer.is_valid(): 60 | order = serializer.save() 61 | 62 | notify(OrderSerializer(order), 63 | NotificationType.ORDER_RECEIVED) 64 | 65 | return Response( 66 | {'order_id': order.id}, 67 | status=status.HTTP_201_CREATED) 68 | 69 | return Response(status=status.HTTP_400_BAD_REQUEST) 70 | 71 | 72 | def cancel_order(request, order_id): 73 | order = get_object_or_404(Order, order_id=order_id) 74 | 75 | return set_status_handler( 76 | lambda: Order.objects.cancel_order(order) 77 | ) 78 | 79 | 80 | def set_next_status(request, order_id): 81 | order = get_object_or_404(Order, order_id=order_id) 82 | 83 | return set_status_handler( 84 | lambda: Order.objects.set_next_status(order) 85 | ) 86 | 87 | 88 | def set_status(request, order_id, status_id): 89 | order = get_object_or_404(Order, order_id=order_id) 90 | 91 | try: 92 | status = Status(status_id) 93 | except ValueError: 94 | return HttpResponse( 95 | 'The status value is invalid.', 96 | status=status.HTTP_400_BAD_REQUEST) 97 | 98 | return set_status_handler( 99 | lambda: Order.objects.set_status(order, status) 100 | ) 101 | 102 | 103 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/app.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import curses.panel 3 | from curses import wrapper 4 | from curses.textpad import Textbox 5 | from curses.textpad import rectangle 6 | 7 | from client import Menu 8 | from client import DataManager 9 | 10 | 11 | def show_search_screen(stdscr): 12 | curses.curs_set(1) 13 | stdscr.addstr(1, 2, "Artist name: (Ctrl-G to search)") 14 | 15 | editwin = curses.newwin(1, 40, 3, 3) 16 | rectangle(stdscr, 2, 2, 4, 44) 17 | stdscr.refresh() 18 | 19 | box = Textbox(editwin) 20 | box.edit() 21 | 22 | criteria = box.gather() 23 | return criteria 24 | 25 | 26 | def clear_screen(stdscr): 27 | stdscr.clear() 28 | stdscr.refresh() 29 | 30 | 31 | def main(stdscr): 32 | 33 | curses.cbreak() 34 | curses.noecho() 35 | stdscr.keypad(True) 36 | 37 | _data_manager = DataManager() 38 | 39 | criteria = show_search_screen(stdscr) 40 | 41 | height, width = stdscr.getmaxyx() 42 | 43 | albums_panel = Menu('List of albums for the selected artist', 44 | (height, width, 0, 0)) 45 | 46 | tracks_panel = Menu('List of tracks for the selected album', 47 | (height, width, 0, 0)) 48 | 49 | artist = _data_manager.search_artist(criteria) 50 | 51 | albums = _data_manager.get_artist_albums(artist['id']) 52 | 53 | clear_screen(stdscr) 54 | 55 | albums_panel.items = albums 56 | albums_panel.init() 57 | albums_panel.update() 58 | albums_panel.show() 59 | 60 | current_panel = albums_panel 61 | 62 | is_running = True 63 | 64 | while is_running: 65 | curses.doupdate() 66 | curses.panel.update_panels() 67 | 68 | key = stdscr.getch() 69 | 70 | action = current_panel.handle_events(key) 71 | 72 | if action is not None: 73 | action_result = action() 74 | if current_panel == albums_panel and action_result is not None: 75 | _id, uri = action_result 76 | tracks = _data_manager.get_album_tracklist(_id) 77 | current_panel.hide() 78 | current_panel = tracks_panel 79 | current_panel.items = tracks 80 | current_panel.init() 81 | current_panel.show() 82 | elif current_panel == tracks_panel and action_result is not None: 83 | _id, uri = action_result 84 | _data_manager.play(uri) 85 | 86 | if key == curses.KEY_F2: 87 | current_panel.hide() 88 | criteria = show_search_screen(stdscr) 89 | artist = _data_manager.search_artist(criteria) 90 | albums = _data_manager.get_artist_albums(artist['id']) 91 | 92 | clear_screen(stdscr) 93 | current_panel = albums_panel 94 | current_panel.items = albums 95 | current_panel.init() 96 | current_panel.show() 97 | 98 | if key == ord('q') or key == ord('Q'): 99 | is_running = False 100 | 101 | current_panel.update() 102 | 103 | 104 | try: 105 | wrapper(main) 106 | except KeyboardInterrupt: 107 | print('Thanks for using this app, bye!') -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/auth.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import requests 4 | import base64 5 | import json 6 | 7 | from .authorization import Authorization 8 | from pytify.core import BadRequestError 9 | from .auth_method import AuthMethod 10 | 11 | 12 | def get_auth_key(client_id, client_secret): 13 | byte_keys = bytes(f'{client_id}:{client_secret}', 'utf-8') 14 | encoded_key = base64.b64encode(byte_keys) 15 | return encoded_key.decode('utf-8') 16 | 17 | def _authorization_code(conf): 18 | 19 | current_dir = os.path.abspath(os.curdir) 20 | file_path = os.path.join(current_dir, '.pytify') 21 | 22 | auth_key = get_auth_key(conf.client_id, conf.client_secret) 23 | 24 | try: 25 | with open(file_path, mode='r', encoding='UTF-8') as file: 26 | refresh_token = file.readline() 27 | 28 | if refresh_token: 29 | return _refresh_access_token(auth_key, refresh_token) 30 | except IOError: 31 | raise IOError(('It seems you have not authorize the application ' 32 | 'yet. The file .pytify was not found.')) 33 | 34 | 35 | def _refresh_access_token(auth_key, refresh_token): 36 | 37 | headers = {'Authorization': f'Basic {auth_key}', } 38 | 39 | options = { 40 | 'refresh_token': refresh_token, 41 | 'grant_type': 'refresh_token', 42 | } 43 | 44 | response = requests.post( 45 | 'https://accounts.spotify.com/api/token', 46 | headers=headers, 47 | data=options 48 | ) 49 | 50 | content = json.loads(response.content.decode('utf-8')) 51 | 52 | if not response.ok: 53 | error_description = content.get('error_description', None) 54 | raise BadRequestError(error_description) 55 | 56 | access_token = content.get('access_token', None) 57 | token_type = content.get('token_type', None) 58 | scope = content.get('scope', None) 59 | expires_in = content.get('expires_in', None) 60 | 61 | return Authorization(access_token, token_type, expires_in, scope, None) 62 | 63 | 64 | 65 | 66 | def _client_credentials(conf): 67 | 68 | auth_key = get_auth_key(conf.client_id, conf.client_secret) 69 | 70 | headers = {'Authorization': f'Basic {auth_key}', } 71 | 72 | options = { 73 | 'grant_type': 'client_credentials', 74 | 'json': True, 75 | } 76 | 77 | response = requests.post( 78 | 'https://accounts.spotify.com/api/token', 79 | headers=headers, 80 | data=options 81 | ) 82 | 83 | content = json.loads(response.content.decode('utf-8')) 84 | 85 | if response.status_code == 400: 86 | error_description = content.get('error_description', None) 87 | raise BadRequestError(error_description) 88 | 89 | access_token = content.get('access_token', None) 90 | token_type = content.get('token_type', None) 91 | expires_in = content.get('expires_in', None) 92 | scope = content.get('scope', None) 93 | 94 | return Authorization(access_token, token_type, expires_in, scope, None) 95 | 96 | def authenticate(conf): 97 | if conf.auth_method == AuthMethod.CLIENT_CREDENTIALS: 98 | return _client_credentials(conf) 99 | 100 | return _authorization_code(conf) -------------------------------------------------------------------------------- /Chapter07/gamestore/main/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import AuthenticationForm 3 | from django.contrib.auth.models import User 4 | 5 | from django.forms import inlineformset_factory 6 | from .models import ShoppingCartItem 7 | from .models import ShoppingCart 8 | 9 | 10 | def validate_unique_user(error_message, **criteria): 11 | existent_user = User.objects.filter(**criteria) 12 | 13 | if existent_user: 14 | raise forms.ValidationError(error_message) 15 | 16 | 17 | class AuthenticationForm(AuthenticationForm): 18 | username = forms.CharField( 19 | max_length=50, 20 | widget=forms.TextInput({ 21 | 'class': 'form-control', 22 | 'placeholder': 'User name' 23 | }) 24 | ) 25 | 26 | password = forms.CharField( 27 | label="Password", 28 | widget=forms.PasswordInput({ 29 | 'class': 'form-control', 30 | 'placeholder': 'Password' 31 | }) 32 | ) 33 | 34 | 35 | class SignupForm(forms.Form): 36 | username = forms.CharField( 37 | max_length=10, 38 | widget=forms.TextInput({ 39 | 'class': 'form-control', 40 | 'placeholder': 'First name' 41 | }) 42 | ) 43 | 44 | first_name = forms.CharField( 45 | max_length=100, 46 | widget=forms.TextInput({ 47 | 'class': 'form-control', 48 | 'placeholder': 'First name' 49 | }) 50 | ) 51 | 52 | last_name = forms.CharField( 53 | max_length=200, 54 | widget=forms.TextInput({ 55 | 'class': 'form-control', 56 | 'placeholder': 'Last name' 57 | }) 58 | ) 59 | 60 | email = forms.CharField( 61 | max_length=200, 62 | widget=forms.TextInput({ 63 | 'class': 'form-control', 64 | 'placeholder': 'Email' 65 | }) 66 | ) 67 | 68 | password = forms.CharField( 69 | min_length=6, 70 | max_length=10, 71 | widget=forms.PasswordInput({ 72 | 'class': 'form-control', 73 | 'placeholder': 'Password' 74 | }) 75 | ) 76 | 77 | repeat_password = forms.CharField( 78 | min_length=6, 79 | max_length=10, 80 | widget=forms.PasswordInput({ 81 | 'class': 'form-control', 82 | 'placeholder': 'Repeat password' 83 | }) 84 | ) 85 | 86 | def clean_username(self): 87 | username = self.cleaned_data['username'] 88 | 89 | validate_unique_user( 90 | error_message='* Username already in use', 91 | username=username) 92 | 93 | return username 94 | 95 | def clean_email(self): 96 | email = self.cleaned_data['email'] 97 | 98 | validate_unique_user( 99 | error_message='* Email already in use', 100 | email=email) 101 | 102 | return email 103 | 104 | def clean_repeat_password(self): 105 | password1 = self.cleaned_data['password'] 106 | password2 = self.cleaned_data['repeat_password'] 107 | 108 | if password1 != password2: 109 | raise forms.ValidationError('* Passwords did not match') 110 | 111 | return password1 112 | 113 | 114 | ShoppingCartFormSet = inlineformset_factory( 115 | ShoppingCart, 116 | ShoppingCartItem, 117 | fields=('quantity', 'price_per_unit'), 118 | extra=0, 119 | widgets={ 120 | 'quantity': forms.TextInput({ 121 | 'class': 'form-control quantity', 122 | }), 123 | 'price_per_unit': forms.HiddenInput() 124 | } 125 | ) 126 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import AuthenticationForm 3 | from django.contrib.auth.models import User 4 | 5 | from django.forms import inlineformset_factory 6 | from .models import ShoppingCartItem 7 | from .models import ShoppingCart 8 | 9 | 10 | def validate_unique_user(error_message, **criteria): 11 | existent_user = User.objects.filter(**criteria) 12 | 13 | if existent_user: 14 | raise forms.ValidationError(error_message) 15 | 16 | 17 | class AuthenticationForm(AuthenticationForm): 18 | username = forms.CharField( 19 | max_length=50, 20 | widget=forms.TextInput({ 21 | 'class': 'form-control', 22 | 'placeholder': 'User name' 23 | }) 24 | ) 25 | 26 | password = forms.CharField( 27 | label="Password", 28 | widget=forms.PasswordInput({ 29 | 'class': 'form-control', 30 | 'placeholder': 'Password' 31 | }) 32 | ) 33 | 34 | 35 | class SignupForm(forms.Form): 36 | username = forms.CharField( 37 | max_length=10, 38 | widget=forms.TextInput({ 39 | 'class': 'form-control', 40 | 'placeholder': 'First name' 41 | }) 42 | ) 43 | 44 | first_name = forms.CharField( 45 | max_length=100, 46 | widget=forms.TextInput({ 47 | 'class': 'form-control', 48 | 'placeholder': 'First name' 49 | }) 50 | ) 51 | 52 | last_name = forms.CharField( 53 | max_length=200, 54 | widget=forms.TextInput({ 55 | 'class': 'form-control', 56 | 'placeholder': 'Last name' 57 | }) 58 | ) 59 | 60 | email = forms.CharField( 61 | max_length=200, 62 | widget=forms.TextInput({ 63 | 'class': 'form-control', 64 | 'placeholder': 'Email' 65 | }) 66 | ) 67 | 68 | password = forms.CharField( 69 | min_length=6, 70 | max_length=10, 71 | widget=forms.PasswordInput({ 72 | 'class': 'form-control', 73 | 'placeholder': 'Password' 74 | }) 75 | ) 76 | 77 | repeat_password = forms.CharField( 78 | min_length=6, 79 | max_length=10, 80 | widget=forms.PasswordInput({ 81 | 'class': 'form-control', 82 | 'placeholder': 'Repeat password' 83 | }) 84 | ) 85 | 86 | def clean_username(self): 87 | username = self.cleaned_data['username'] 88 | 89 | validate_unique_user( 90 | error_message='* Username already in use', 91 | username=username) 92 | 93 | return username 94 | 95 | def clean_email(self): 96 | email = self.cleaned_data['email'] 97 | 98 | validate_unique_user( 99 | error_message='* Email already in use', 100 | email=email) 101 | 102 | return email 103 | 104 | def clean_repeat_password(self): 105 | password1 = self.cleaned_data['password'] 106 | password2 = self.cleaned_data['repeat_password'] 107 | 108 | if password1 != password2: 109 | raise forms.ValidationError('* Passwords did not match') 110 | 111 | return password1 112 | 113 | 114 | ShoppingCartFormSet = inlineformset_factory( 115 | ShoppingCart, 116 | ShoppingCartItem, 117 | fields=('quantity', 'price_per_unit'), 118 | extra=0, 119 | widgets={ 120 | 'quantity': forms.TextInput({ 121 | 'class': 'form-control quantity', 122 | }), 123 | 'price_per_unit': forms.HiddenInput() 124 | } 125 | ) 126 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class GamePlatform(models.Model): 6 | name = models.CharField(max_length=100) 7 | 8 | def __str__(self): 9 | return self.name 10 | 11 | 12 | class GameManager(models.Manager): 13 | 14 | def get_highlighted(self): 15 | return self.filter(highlighted=True) 16 | 17 | def get_not_highlighted(self): 18 | return self.filter(highlighted=False) 19 | 20 | def get_by_platform(self, platform): 21 | return self.filter(gameplatform__name__iexact=platform) 22 | 23 | 24 | class Game(models.Model): 25 | class Meta: 26 | ordering = ['-highlighted', 'name'] 27 | 28 | name = models.CharField(max_length=100) 29 | 30 | release_year = models.IntegerField(null=True) 31 | 32 | developer = models.CharField(max_length=100) 33 | 34 | published_by = models.CharField(max_length=100) 35 | 36 | image = models.ImageField( 37 | upload_to='images/', 38 | default='images/placeholder.png', 39 | max_length=100) 40 | 41 | gameplatform = models.ForeignKey(GamePlatform, 42 | null=False, 43 | on_delete=models.CASCADE) 44 | 45 | highlighted = models.BooleanField(default=False) 46 | 47 | objects = GameManager() 48 | 49 | def __str__(self): 50 | return f'{self.gameplatform.name} - {self.name}' 51 | 52 | 53 | class PriceList(models.Model): 54 | added_at = models.DateTimeField(auto_now_add=True) 55 | 56 | last_updated = models.DateTimeField(auto_now=True) 57 | 58 | price_per_unit = models.DecimalField(max_digits=9, 59 | decimal_places=2, default=0) 60 | 61 | game = models.OneToOneField( 62 | Game, 63 | on_delete=models.CASCADE, 64 | primary_key=True) 65 | 66 | def __str__(self): 67 | return self.game.name 68 | 69 | 70 | class ShoppingCartManager(models.Manager): 71 | 72 | def get_by_id(self, id): 73 | return self.get(pk=id) 74 | 75 | def get_by_user(self, user): 76 | return self.get(user_id=user.id) 77 | 78 | def create_cart(self, user): 79 | new_cart = self.create(user=user) 80 | return new_cart 81 | 82 | 83 | class ShoppingCart(models.Model): 84 | user = models.ForeignKey(User, 85 | null=False, 86 | on_delete=models.CASCADE) 87 | 88 | objects = ShoppingCartManager() 89 | 90 | def __str__(self): 91 | return f'{self.user.username}\'s shopping cart' 92 | 93 | 94 | class ShoppingCartItemManager(models.Manager): 95 | 96 | def get_items(self, cart): 97 | return self.filter(cart_id=cart.id) 98 | 99 | def get_existing_item(self, cart, game): 100 | try: 101 | return self.get(cart_id=cart.id, 102 | game_id=game.id) 103 | except ShoppingCartItem.DoesNotExist: 104 | return None 105 | 106 | 107 | class ShoppingCartItem(models.Model): 108 | quantity = models.IntegerField(null=False) 109 | 110 | price_per_unit = models.DecimalField(max_digits=9, 111 | decimal_places=2, 112 | default=0) 113 | 114 | cart = models.ForeignKey(ShoppingCart, 115 | null=False, 116 | on_delete=models.CASCADE) 117 | game = models.ForeignKey(Game, 118 | null=False, 119 | on_delete=models.CASCADE) 120 | 121 | objects = ShoppingCartItemManager() 122 | -------------------------------------------------------------------------------- /Chapter07/gamestore/gamestore/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for gamestore project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '16bmv9^@d8014cmq^f*)6p3%rdq3=k+9ie9#p&uhg-l43&3!f)' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'django.contrib.humanize', 41 | 'main', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'gamestore.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [ 60 | os.path.join(BASE_DIR, 'templates'), 61 | ], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'gamestore.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 109 | 110 | LANGUAGE_CODE = 'en-us' 111 | 112 | TIME_ZONE = 'UTC' 113 | 114 | USE_I18N = True 115 | 116 | USE_L10N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 123 | 124 | STATIC_URL = '/static/' 125 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ] 126 | MEDIA_ROOT = os.path.join(BASE_DIR, 'static') 127 | 128 | LOGIN_REDIRECT_URL = '/' -------------------------------------------------------------------------------- /Chapter08/microservices/order/order/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for order project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'eluqy6%1-!$ta&nq*c0&lm#n06t8umu-p6*m#ik!&v4lbr5ot@' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'main', 41 | 'rest_framework', 42 | 'rest_framework.authtoken', 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | REST_FRAMEWORK = { 56 | 'DEFAULT_PERMISSION_CLASSES': ( 57 | 'rest_framework.permissions.IsAuthenticated', 58 | ), 59 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 60 | 'rest_framework.authentication.TokenAuthentication', 61 | ) 62 | } 63 | 64 | ROOT_URLCONF = 'order.urls' 65 | 66 | TEMPLATES = [ 67 | { 68 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 69 | 'DIRS': [], 70 | 'APP_DIRS': True, 71 | 'OPTIONS': { 72 | 'context_processors': [ 73 | 'django.template.context_processors.debug', 74 | 'django.template.context_processors.request', 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.contrib.messages.context_processors.messages', 77 | ], 78 | }, 79 | }, 80 | ] 81 | 82 | WSGI_APPLICATION = 'order.wsgi.application' 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.sqlite3', 91 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 92 | } 93 | } 94 | 95 | 96 | # Password validation 97 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 98 | 99 | AUTH_PASSWORD_VALIDATORS = [ 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 111 | }, 112 | ] 113 | 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 117 | 118 | LANGUAGE_CODE = 'en-us' 119 | 120 | TIME_ZONE = 'UTC' 121 | 122 | USE_I18N = True 123 | 124 | USE_L10N = True 125 | 126 | USE_TZ = True 127 | 128 | 129 | # Static files (CSS, JavaScript, Images) 130 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 131 | 132 | STATIC_URL = '/static/' 133 | --------------------------------------------------------------------------------