Skip to content

Commit a7a838b

Browse files
authored
feat: add service to list products and elements (#35)
1 parent 64e001c commit a7a838b

File tree

6 files changed

+132
-7
lines changed

6 files changed

+132
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ All notable changes in **python-transip** are documented below.
88
- The `transip.v6.objects.InvoiceItemService` service to allow listing all invoice items on a `transip.v6.objects.Invoice` object.
99
- The `transip.mixins.ObjectUpdateMixin` mixin to allow calling `update()` on API object directly.
1010
- Allow an invoice to be written to a PDF file by calling the `pdf()` method on a `transip.v6.objects.Invoice` object.
11+
- The `transip.v6.objects.ProductService` service to allow listing all products as a `transip.v6.objects.Product` object.
1112

1213
[Unreleased]: https://github.com/roaldnefs/python-transip/compare/v0.3.0...HEAD

tests/fixtures/general.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"products": {
1414
"vps": [
1515
{
16-
"name": "example-product-name",
16+
"name": "vps-bladevps-x4",
1717
"description": "This is an example product",
1818
"price": 499,
1919
"recurringPrice": 799
@@ -58,7 +58,7 @@
5858
},
5959
{
6060
"method": "GET",
61-
"url": "https://api.transip.nl/v6/products/ipv4Addresses/elements",
61+
"url": "https://api.transip.nl/v6/products/vps-bladevps-x4/elements",
6262
"json": {
6363
"productElements": [
6464
{

tests/services/test_products.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2021 Roald Nefs <[email protected]>
4+
#
5+
# This file is part of python-transip.
6+
#
7+
# python-transip is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# python-transip is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
19+
20+
from typing import List, Tuple, Any, Dict, Optional
21+
import responses # type: ignore
22+
import unittest
23+
import tempfile
24+
import os
25+
26+
from transip import TransIP
27+
from transip.v6.objects import Product, ProductElement
28+
from tests.utils import load_responses_fixtures
29+
30+
31+
class ProductsTest(unittest.TestCase):
32+
"""Test the ProductsService."""
33+
34+
client: TransIP
35+
36+
@classmethod
37+
def setUpClass(cls) -> None:
38+
"""Set up a minimal TransIP client for using the invoice services."""
39+
cls.client = TransIP(access_token='ACCESS_TOKEN')
40+
41+
def setUp(self) -> None:
42+
"""Setup mocked responses for the '/products' endpoint."""
43+
load_responses_fixtures("general.json")
44+
45+
@responses.activate
46+
def test_list(self) -> None:
47+
"""Check if the products can be listed."""
48+
products: List[Product] = self.client.products.list() # type: ignore
49+
50+
self.assertEqual(len(products), 5)
51+
52+
@responses.activate
53+
def test_elements_list(self) -> None:
54+
"""Check if the elements of a product can be listed."""
55+
products: List[Product] = self.client.products.list() # type: ignore
56+
product: Optional[Product] = None
57+
58+
# Find the correct product as the API doesn't provide an option to
59+
# retrieve a single product
60+
for entry in products:
61+
if entry.get_id() == 'vps-bladevps-x4':
62+
product = entry
63+
break
64+
65+
self.assertIsNotNone(product)
66+
67+
elements = product.elements.list() # type: ignore
68+
self.assertEqual(len(elements), 1)
69+
self.assertEqual(elements[0].get_id(), 'ipv4Addresses') # type: ignore

transip/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ def __init__(
9393
self.availability_zones: Type['ApiService'] = (
9494
objects.AvailabilityZoneService(self) # type: ignore
9595
)
96+
self.products: Type['ApiService'] = (
97+
objects.ProductService(self) # type: ignore
98+
)
9699
self.domains: Type['ApiService'] = (
97100
objects.DomainService(self) # type: ignore
98101
)

transip/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2020 Roald Nefs <[email protected]>
3+
# Copyright (C) 2020, 2021 Roald Nefs <[email protected]>
44
#
55
# This file is part of python-transip.
66
#
@@ -17,7 +17,7 @@
1717
# You should have received a copy of the GNU Lesser General Public License
1818
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
1919

20-
from typing import Optional, Type, Any
20+
from typing import Optional, Type, Any, Union
2121

2222
from transip import TransIP
2323

@@ -61,8 +61,8 @@ def __repr__(self) -> str:
6161
def __dir__(self):
6262
return super().__dir__() + list(self.attrs)
6363

64-
def get_id(self) -> Any:
65-
"""Returns the id of the object."""
64+
def get_id(self) -> Union[Optional[int], Optional[str]]:
65+
"""Returns the ID of the object."""
6666
if self._id_attr and hasattr(self, self._id_attr):
6767
return getattr(self, self._id_attr)
6868
return None

transip/v6/objects.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import os
2121
import base64
2222

23-
from typing import Optional, Type
23+
from typing import Optional, Type, List
2424

2525
from transip.base import ApiService, ApiObject
2626
from transip.mixins import (
@@ -48,6 +48,58 @@ def test(self):
4848
return False
4949

5050

51+
class ProductElement(ApiObject):
52+
53+
_id_attr: str = "name"
54+
55+
56+
class ProductElementService(ListMixin, ApiService):
57+
"""Service to manage elements of a product."""
58+
59+
_path: str = "/products/{parent_id}/elements"
60+
_obj_cls: Optional[Type[ApiObject]] = ProductElement
61+
62+
_resp_list_attr: str = "productElements"
63+
64+
65+
class Product(ApiObject):
66+
67+
_id_attr: Optional[str] = "name"
68+
69+
@property
70+
def elements(self) -> ProductElementService:
71+
"""Return the service to manage the elements of the product."""
72+
return ProductElementService(
73+
self.service.client,
74+
parent=self # type: ignore
75+
)
76+
77+
78+
class ProductService(ListMixin, ApiService):
79+
"""Service to manage products."""
80+
81+
_path: str = "/products"
82+
_obj_cls: Optional[Type[ApiObject]] = Product
83+
84+
_resp_list_attr: str = "products"
85+
86+
def list(self) -> List[Type[ApiObject]]:
87+
"""
88+
Retrieve a list of products.
89+
90+
Overwrites the default list() method of the ListMixin as the products
91+
are stored in further down in the result dictionary.
92+
"""
93+
objs: List[Type[ApiObject]] = []
94+
data = self.client.get(self.path)[self._resp_list_attr]
95+
# Loop over the individual product lists of all product categories,
96+
# e.g. vps, haip
97+
for obj_list in data.values():
98+
for obj in obj_list:
99+
objs.append(self._obj_cls(self, obj)) # type: ignore
100+
return objs
101+
102+
51103
class AvailabilityZone(ApiObject):
52104

53105
_id_attr: str = "name"

0 commit comments

Comments
 (0)