Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nameko challenge - Wilson Pereira #2

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion devops/nex-smoketest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,17 @@ ID=$(echo ${ORDER_ID} | jq '.id')

# Test: Get Order back
echo "=== Getting Order ==="
curl -s "${STD_APP_URL}/orders/${ID}" | jq -r
curl -s "${STD_APP_URL}/orders/${ID}" | jq -r

# Test: List Orders
echo "=== Listing Orders ==="
curl -s "${STD_APP_URL}/orders" | jq .

# Test: Delete Product
echo "=== Deleting product id: the_odyssey ==="
curl -s -X DELETE "${STD_APP_URL}/products/the_odyssey"
echo

# Test: Try to Get the Deleted Product
echo "=== Attempting to Get deleted product id: the_odyssey ==="
curl -s -o /dev/null -w "%{http_code}" "${STD_APP_URL}/products/the_odyssey"
23 changes: 19 additions & 4 deletions gateway/gateway/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class CreateOrderDetailSchema(Schema):


class CreateOrderSchema(Schema):
order_details = fields.Nested(
CreateOrderDetailSchema, many=True, required=True
)
order_details = fields.Nested(CreateOrderDetailSchema, many=True, required=True)


class ProductSchema(Schema):
Expand All @@ -22,7 +20,6 @@ class ProductSchema(Schema):


class GetOrderSchema(Schema):

class OrderDetail(Schema):
id = fields.Int()
quantity = fields.Int()
Expand All @@ -33,3 +30,21 @@ class OrderDetail(Schema):

id = fields.Int()
order_details = fields.Nested(OrderDetail, many=True)


class ListOrderResponseSchema(Schema):
class ListOrderDetail(Schema):
id = fields.Int()
quantity = fields.Int()
product_id = fields.Str()
image = fields.Str()
price = fields.Decimal(as_string=True)

id = fields.Int()
order_details = fields.Nested(ListOrderDetail, many=True)


class ListOrdersRequestSchema(Schema):
ids = fields.List(fields.Str(), required=False)
page = fields.Int(missing=1)
per_page = fields.Int(missing=10)
173 changes: 119 additions & 54 deletions gateway/gateway/service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json

import cachetools
from marshmallow import ValidationError
from nameko import config
from nameko.exceptions import BadRequest
Expand All @@ -8,36 +9,32 @@

from gateway.entrypoints import http
from gateway.exceptions import OrderNotFound, ProductNotFound
from gateway.schemas import CreateOrderSchema, GetOrderSchema, ProductSchema
from gateway.schemas import (
CreateOrderSchema,
GetOrderSchema,
ProductSchema,
ListOrdersRequestSchema,
ListOrderResponseSchema,
)


class GatewayService(object):
"""
Service acts as a gateway to other services over http.
"""

name = 'gateway'
name = "gateway"

orders_rpc = RpcProxy('orders')
products_rpc = RpcProxy('products')
orders_rpc = RpcProxy("orders")
products_rpc = RpcProxy("products")

@http(
"GET", "/products/<string:product_id>",
expected_exceptions=ProductNotFound
)
@http("GET", "/products/<string:product_id>", expected_exceptions=ProductNotFound)
def get_product(self, request, product_id):
"""Gets product by `product_id`
"""
"""Gets product by `product_id`"""
product = self.products_rpc.get(product_id)
return Response(
ProductSchema().dumps(product).data,
mimetype='application/json'
)

@http(
"POST", "/products",
expected_exceptions=(ValidationError, BadRequest)
)
return Response(ProductSchema().dumps(product).data, mimetype="application/json")

@http("POST", "/products", expected_exceptions=(ValidationError, BadRequest))
def create_product(self, request):
"""Create a new product - product data is posted as json

Expand Down Expand Up @@ -70,9 +67,26 @@ def create_product(self, request):

# Create the product
self.products_rpc.create(product_data)
return Response(
json.dumps({'id': product_data['id']}), mimetype='application/json'
)
return Response(json.dumps({"id": product_data["id"]}), mimetype="application/json")

@http("DELETE", "/products/<string:product_id>", expected_exceptions=(ProductNotFound, BadRequest))
def delete_product(self, request, product_id):
"""Delete a product by its `product_id`.

Args:
product_id (str): The ID of the product to be deleted.

Returns:
Response: A JSON response indicating success or an error message.
"""

try:
self.products_rpc.delete(product_id)
except ProductNotFound as exc:
raise ProductNotFound(str(exc))

response_data = {"message": "Product with ID {} deleted.".format(product_id)}
return Response(json.dumps(response_data), mimetype="application/json")

@http("GET", "/orders/<int:order_id>", expected_exceptions=OrderNotFound)
def get_order(self, request, order_id):
Expand All @@ -82,10 +96,7 @@ def get_order(self, request, order_id):
products-service.
"""
order = self._get_order(order_id)
return Response(
GetOrderSchema().dumps(order).data,
mimetype='application/json'
)
return Response(GetOrderSchema().dumps(order).data, mimetype="application/json")

def _get_order(self, order_id):
# Retrieve order data from the orders service.
Expand All @@ -94,25 +105,75 @@ def _get_order(self, order_id):
order = self.orders_rpc.get_order(order_id)

# Retrieve all products from the products service
product_map = {prod['id']: prod for prod in self.products_rpc.list()}
product_map = {prod["id"]: prod for prod in self.products_rpc.list()}

# get the configured image root
image_root = config['PRODUCT_IMAGE_ROOT']
image_root = config["PRODUCT_IMAGE_ROOT"]

# Enhance order details with product and image details.
for item in order['order_details']:
product_id = item['product_id']
for item in order["order_details"]:
product_id = item["product_id"]

item['product'] = product_map[product_id]
item["product"] = product_map[product_id]
# Construct an image url.
item['image'] = '{}/{}.jpg'.format(image_root, product_id)
item["image"] = "{}/{}.jpg".format(image_root, product_id)

return order

@http(
"POST", "/orders",
expected_exceptions=(ValidationError, ProductNotFound, BadRequest)
)
@http("GET", "/orders", expected_exceptions=BadRequest)
def list_orders(self, request):
"""
List orders based on query parameters.

Args:
request: The HTTP request object.

Returns:
A list of orders in JSON format.

Raises:
BadRequest: If there's a validation error or invalid JSON in the request.
"""
data = request.get_data(as_text=True) or "{}"
try:
validated_data = ListOrdersRequestSchema().loads(data).data
ids = validated_data.get("ids")
page = validated_data.get("page")
per_page = validated_data.get("per_page")
except ValidationError as error:
raise BadRequest("Validation error: {}".format(error))
except ValueError as exc:
raise BadRequest("Invalid JSON: {}".format(exc))

orders = self._list_orders(ids, page, per_page)
return Response(ListOrderResponseSchema(many=True).dumps(orders).data, mimetype="application/json")

def _list_orders(self, ids=None, page=1, per_page=10):
"""
Retrieve and enhance a list of orders.

Args:
ids (list): A list of order IDs to filter by (default is None).
page (int): The page number for pagination (default is 1).
per_page (int): The number of orders per page (default is 10).

Returns:
A list of orders with enhanced details, including product images.

"""
# Retrieve orders based on ids from the orders service.
orders = self.orders_rpc.list_orders(ids, page, per_page)

# Enhance order details with product images.
image_root = config["PRODUCT_IMAGE_ROOT"]
for order in orders:
for item in order["order_details"]:
product_id = item["product_id"]
item["image"] = "{}/{}.jpg".format(image_root, product_id)

return orders

@http("POST", "/orders", expected_exceptions=(ValidationError, ProductNotFound, BadRequest))
def create_order(self, request):
"""Create a new order - order data is posted as json

Expand Down Expand Up @@ -143,32 +204,36 @@ def create_order(self, request):
schema = CreateOrderSchema(strict=True)

try:
# load input data through a schema (for validation)
# Note - this may raise `ValueError` for invalid json,
# or `ValidationError` if data is invalid.
order_data = schema.loads(request.get_data(as_text=True)).data
except ValueError as exc:
raise BadRequest("Invalid json: {}".format(exc))
raise BadRequest("Invalid JSON: {}".format(exc))

order_details = order_data["order_details"]
valid_product_ids = {prod["id"] for prod in self.products_rpc.list()}

invalid_product_ids = {item["product_id"] for item in order_details} - valid_product_ids
if invalid_product_ids:
raise ProductNotFound("Product Id {}".format(", ".join(invalid_product_ids)))

# Create the order without unnecessary product data retrieval
serialized_order_details = [
{"product_id": item["product_id"], "price": item["price"], "quantity": item["quantity"]}
for item in order_details
]

# Create the order
# Note - this may raise `ProductNotFound`
id_ = self._create_order(order_data)
return Response(json.dumps({'id': id_}), mimetype='application/json')
id_ = self._create_order({"order_details": serialized_order_details}) # Pass the correct structure
return Response(json.dumps({"id": id_}), mimetype="application/json")

def _create_order(self, order_data):
# check order product ids are valid
valid_product_ids = {prod['id'] for prod in self.products_rpc.list()}
for item in order_data['order_details']:
if item['product_id'] not in valid_product_ids:
raise ProductNotFound(
"Product Id {}".format(item['product_id'])
)
valid_product_ids = {prod["id"] for prod in self.products_rpc.list()}
for item in order_data["order_details"]:
if item["product_id"] not in valid_product_ids:
raise ProductNotFound("Product Id {}".format(item["product_id"]))

# Call orders-service to create the order.
# Dump the data through the schema to ensure the values are serialized
# correctly.
serialized_data = CreateOrderSchema().dump(order_data).data
result = self.orders_rpc.create_order(
serialized_data['order_details']
)
return result['id']
result = self.orders_rpc.create_order(serialized_data["order_details"])
return result["id"]
Loading