Created
October 13, 2019 21:47
-
-
Save victorusachev/2b0d9a679fcb890c7364843f20148a26 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import logging | |
| import random | |
| from dataclasses import dataclass, field | |
| from decimal import Decimal | |
| from typing import Callable, Iterable, List, Union | |
| _logger = logging.getLogger(__name__) | |
| MENU = [ | |
| { | |
| 'description': 'Томатный соус, моцарелла и орегано.', | |
| 'name': 'Пицца "Маргарита" (Margherita)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, чеснок и базилик.', | |
| 'name': 'Пицца "Маринара" (Marinara)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, грибы, ветчина, артишоки, оливки и орегано.', | |
| 'name': 'Пицца "Четыре сезона" (Quattro Stagioni)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, пармезан, яйца, бекон.', | |
| 'name': 'Пицца "Карбонара" (Carbonara)' | |
| }, | |
| { | |
| 'description': 'Томатный соус и морепродукты.', | |
| 'name': 'Пицца с морепродуктами (Frutti di Mare)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, пармезан, сыр горгонзола, артишоки и орегано.', | |
| 'name': 'Пицца "Четыре сыра" (Quattro Formaggi)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла и пармская ветчина.', 'name': 'Пицца "Крудо" (Crudo)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, орегано, анчоусы.', | |
| 'name': 'Пицца "Неаполетано" (Napoletana)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, орегано, лук.', | |
| 'name': 'Пицца по-апулийски (Pugliese)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, грибы, пепперони и Страккино (мягкий сыр).', | |
| 'name': 'Пицца "Монтанара" (Montanara)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, баклажаны, отварной картофель и колбаса.', | |
| 'name': 'Пицца "Эмилиана" (Emiliana)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, анчоусы, каперсы и орегано.', | |
| 'name': 'Римская пицца (Romana)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, перец, горошек, порчетта (итальянская свинина на вертеле).', | |
| 'name': 'Фермерская пицца (Fattoria)' | |
| }, | |
| { | |
| 'description': 'Оливковое масло и розмарин.', 'name': 'Скьяччата (Schiacciata)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, ветчина, орегано.', | |
| 'name': 'Пицца с прошутто (Prosciutto)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, колбаса и картофель фри.', | |
| 'name': 'Пицца "Американо" (Americana)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, ветчина, грибы.', | |
| 'name': 'Пицца с прошутто и грибами (Prosciutto e Funghi)' | |
| }, | |
| { | |
| 'description': 'Моцарелла, шпинат, сыр рикотта и пармезан.', | |
| 'name': 'Пицца силачей или пицца Папайя (Braccio di Ferro)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, сыр пекорино и острая салями.', | |
| 'name': 'Пицца "Сардиния" (Sarda)' | |
| }, | |
| { | |
| 'description': 'Томатный соус, моцарелла, тунец, лук.', | |
| 'name': 'Пицца с тунцом (Tonno)' | |
| }, | |
| ] | |
| class ApplicationError(Exception): | |
| pass | |
| class ApiError(ApplicationError): | |
| pass | |
| class DBError(ApplicationError): | |
| pass | |
| class MultipleObjectsReturned(DBError): | |
| pass | |
| class ObjectAlreadyExists(DBError): | |
| pass | |
| @dataclass | |
| class User: | |
| username: str | |
| def __hash__(self): | |
| return hash(self.username) | |
| @dataclass | |
| class Product: | |
| name: str | |
| quantity: Decimal | |
| description: str = None | |
| def __hash__(self): | |
| return hash(self.name) | |
| @dataclass | |
| class CartItem: | |
| product: Product | |
| quantity: Decimal | |
| def __hash__(self): | |
| return hash(self.product) | |
| @dataclass | |
| class OrderItem: | |
| product: Product | |
| quantity: Decimal | |
| def __hash__(self): | |
| return hash(self.product) | |
| @dataclass | |
| class Order: | |
| user: User | |
| items: List[OrderItem] | |
| def __hash__(self): | |
| return hash(self.user) | |
| @dataclass | |
| class Cart: | |
| user: User | |
| items: List[CartItem] = field(default_factory=list) | |
| def __hash__(self): | |
| return hash(self.user) | |
| @property | |
| def empty(self): | |
| return bool(self.items) | |
| def append(self, product: Product, quantity: Decimal = Decimal('1')) -> None: | |
| if quantity > 0: | |
| self.items.append(CartItem(product, quantity)) | |
| else: | |
| raise ApiError(f'quantity must be greater than 0') | |
| def remove(self, item: CartItem) -> None: | |
| self.items.remove(item) | |
| def form_order(self) -> Order: | |
| if not self.items: | |
| raise ApiError('cart is empty') | |
| items = [OrderItem(i.product, i.quantity) for i in self.items] | |
| order = Order(self.user, items) | |
| self.items.clear() | |
| return order | |
| @dataclass | |
| class Shop: | |
| carts: List[Cart] = field(default_factory=list) | |
| orders: List[Order] = field(default_factory=list) | |
| products: List[Product] = field(default_factory=list) | |
| users: List[User] = field(default_factory=list) | |
| @staticmethod | |
| def _get_object(iterable: Iterable, func: Callable, unique=True): | |
| objects = [o for o in set(iterable) if func(o)] | |
| if objects: | |
| if unique and len(objects) > 1: | |
| raise MultipleObjectsReturned(str(objects)) | |
| return objects[0] | |
| return None | |
| def get_cart(self, user: User) -> Cart: | |
| cart = self._get_object( | |
| self.carts, | |
| lambda b: b.user == user | |
| ) | |
| if cart is None: | |
| cart = Cart(user) | |
| self.carts.append(cart) | |
| return cart | |
| def get_user(self, username) -> Union[User, None]: | |
| user = self._get_object( | |
| self.users, | |
| lambda o: o.username == username | |
| ) | |
| return user | |
| def create_user(self, username) -> User: | |
| user = self.get_user(username) | |
| if user: | |
| raise ObjectAlreadyExists(username) | |
| user = User(username) | |
| self.users.append(user) | |
| return user | |
| def get_menu(self) -> List[Product]: | |
| return [p for p in self.products if p.quantity > 0] | |
| def form_order(self, user: User): | |
| cart = self.get_cart(user) | |
| order = cart.form_order() | |
| if cart.empty: | |
| self.carts.remove(cart) | |
| self.orders.append(order) | |
| return order | |
| # secondary logic | |
| def chose_product(menu: Iterable[Product]) -> Product: | |
| return random.choice(menu) | |
| def chose_quantity(product: Product) -> Decimal: | |
| # check available quantity | |
| return Decimal(random.randint(0, product.quantity)) | |
| def main(): | |
| shop = Shop() | |
| # prepare | |
| shop.products = [ | |
| Product(**p, quantity=Decimal(random.randint(0, 10))) | |
| for p in MENU | |
| ] | |
| try: | |
| user = shop.create_user('nobody') | |
| except ObjectAlreadyExists as ex: | |
| _logger.error(ex) | |
| raise ex | |
| menu = shop.get_menu() | |
| cart = shop.get_cart(user) | |
| for _ in range(0, random.randint(1, 5)): | |
| product = chose_product(menu) | |
| quantity = chose_quantity(product) | |
| try: | |
| cart.append(product, quantity) | |
| except ApiError as ex: | |
| _logger.warning(ex) | |
| try: | |
| order = shop.form_order(user) | |
| print(order) | |
| except ApiError as ex: | |
| _logger.error(ex) | |
| raise ex | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment