From 4cf140dca5c1b8f0f9d4ddf6c3981fa187f5bcb2 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Mon, 4 Mar 2019 12:50:31 +0100 Subject: [PATCH 01/17] Several PyCharm's code inspection warnings removed --- README.md | 20 ++++++++++---------- connect/config.py | 20 ++++++++++---------- connect/models/activation_response.py | 3 ++- connect/models/base.py | 2 +- connect/models/exception.py | 5 ++--- connect/resource/base.py | 8 ++++---- connect/resource/fulfillment.py | 8 ++++---- connect/resource/template.py | 6 +++--- connect/resource/utils.py | 4 ++-- example/example.py | 18 +++++++++--------- setup.py | 4 +++- tests/test_config.py | 10 +++++++--- tests/test_models.py | 4 ++-- 13 files changed, 59 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index c0af046..9066a13 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,16 @@ logger.setLevel("DEBUG") class ExampleRequestProcessor(FulfillmentAutomation): - def process_request(self, request): + def process_request(self, req): # custom logic - if request.type == 'purchase': - for item in request.asset.items: + if req.type == 'purchase': + for item in req.asset.items: if item.quantity > 100000: raise FulfillmentFail( message='Is Not possible to purchase product') - for param in request.asset.params: + for param in req.asset.params: if param.name == 'email' and not param.value: param.value_error = 'Email address has not been provided, please provide one' raise FulfillmentInquire(params=[param]) @@ -66,14 +66,14 @@ class ExampleRequestProcessor(FulfillmentAutomation): return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, ' 'you decided to have an account in our amazing service!') # or - # return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id) + # return TemplateResource().render(pk='TEMPLATE_ID', request_id=req.id) - # aprrove by Template - return ActivationTemplateResponse(template_id="TL-497-535-242") + # approve by Template + # return ActivationTemplateResponse(template_id="TL-497-535-242") # or # return TemplateResource().get(pk='TEMPLATE_ID') - elif request.type == 'change': + elif req.type == 'change': # fail raise FulfillmentFail() else: @@ -82,7 +82,7 @@ class ExampleRequestProcessor(FulfillmentAutomation): if __name__ == '__main__': - request = ExampleRequestProcessor(Config(file='config.json')) - request.process() + processor = ExampleRequestProcessor(Config(filename='config.json')) + processor.process() ``` diff --git a/connect/config.py b/connect/config.py index 9ef9ad9..0274ea5 100644 --- a/connect/config.py +++ b/connect/config.py @@ -15,36 +15,36 @@ def __init__( api_url=None, api_key=None, products=None, - file=None + filename=None ): """ initialization config for public api :param api_url: Public api url :param api_key: Service user ApiKey :param products (optional): Id products - :param file: Config file path + :param filename: Config file path """ # Check arguments - if not file and not any([api_key, api_url]): + if not filename and not any([api_key, api_url]): raise ValueError('Filename or api_key and api_url are expected' 'in Config initialization') if products and not isinstance(products, (str, list)): raise TypeError('Products can be string or string list. Found type ' + type(products).__name__) - # Load config from file name - if file: - if not os.path.exists(file): - raise IOError('Not file `{}` on directory'.format(file)) + # Load config from filename name + if filename: + if not os.path.exists(filename): + raise IOError('Not filename `{}` on directory'.format(filename)) - with open(file) as config_file: + with open(filename) as config_file: configs = config_file.read() try: configs = json.loads(configs) except Exception as ex: - raise TypeError('Invalid config file `{}`\n' - 'ERROR: {}'.format(file, str(ex))) + raise TypeError('Invalid config filename `{}`\n' + 'ERROR: {}'.format(filename, str(ex))) (api_url, api_key, products) = (configs.get('apiEndpoint', ''), configs.get('apiKey', ''), diff --git a/connect/models/activation_response.py b/connect/models/activation_response.py index 5f07430..356c5b6 100644 --- a/connect/models/activation_response.py +++ b/connect/models/activation_response.py @@ -11,7 +11,8 @@ class ActivationTileResponse(object): tile = 'Activation succeeded' - def __init__(self, markdown=None, *args, **kwargs): + # noinspection PyUnusedLocal + def __init__(self, markdown=None, **kwargs): if markdown: try: self.tile = json.loads(markdown) diff --git a/connect/models/base.py b/connect/models/base.py index ad8c0db..b1fda32 100644 --- a/connect/models/base.py +++ b/connect/models/base.py @@ -9,7 +9,7 @@ class BaseModel: - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): self.id = kwargs.get('id') if kwargs: for attr, val in kwargs.items(): diff --git a/connect/models/exception.py b/connect/models/exception.py index 610eb06..cbfe47f 100644 --- a/connect/models/exception.py +++ b/connect/models/exception.py @@ -31,7 +31,7 @@ def __init__(self, *args, **kwargs): class Skip(Message): - def __init__(self, *args, **kwargs): + def __init__(self): self.code = 'skip' @@ -46,5 +46,4 @@ def __init__(self, error=None, *args, **kwargs): "errors": error.errors, }) - super(ServerErrorException, self).__init__( - self.message, *args, **kwargs) + super(ServerErrorException, self).__init__(self.message, *args) diff --git a/connect/resource/base.py b/connect/resource/base.py index bdbb9e0..57f032e 100644 --- a/connect/resource/base.py +++ b/connect/resource/base.py @@ -11,7 +11,7 @@ from connect.logger import function_log, logger from connect.models import BaseSchema, ServerErrorSchema from connect.models.exception import ServerErrorException -from .utils import joinurl +from .utils import join_url class ApiClient(object): @@ -68,7 +68,7 @@ class BaseResource(object): api = None schema = BaseSchema() - def __init__(self, config, *args, **kwargs): + def __init__(self, config): if not self.__class__.resource: raise AttributeError('Resource name not specified in class {}'.format( self.__class__.__name__) + '. Add an attribute `resource` name of the resource') @@ -87,10 +87,10 @@ def build_filter(self): @property def _list_url(self): - return joinurl(self.config.api_url, self.__class__.resource) + return join_url(self.config.api_url, self.__class__.resource) def _obj_url(self, pk): - return joinurl(self._list_url, pk) + return join_url(self._list_url, pk) def __loads_schema(self, response): objects, error = self.schema.loads(response, many=True) diff --git a/connect/resource/fulfillment.py b/connect/resource/fulfillment.py index 596912e..1709bac 100644 --- a/connect/resource/fulfillment.py +++ b/connect/resource/fulfillment.py @@ -11,7 +11,7 @@ from connect.models import FulfillmentSchema, Param from .base import BaseResource from .template import TemplateResource -from .utils import joinurl +from .utils import join_url class FulfillmentResource(BaseResource): @@ -29,16 +29,16 @@ def build_filter(self): @function_log def approve(self, pk, data): - url = joinurl(self._obj_url(pk), 'approve/') + url = join_url(self._obj_url(pk), 'approve/') return self.api.post(url=url, data=json.dumps(data if data else {})) @function_log def inquire(self, pk): - return self.api.post(url=joinurl(self._obj_url(pk), 'inquire/'), data=json.dumps({})) + return self.api.post(url=join_url(self._obj_url(pk), 'inquire/'), data=json.dumps({})) @function_log def fail(self, pk, reason): - url = joinurl(self._obj_url(pk), 'fail/') + url = join_url(self._obj_url(pk), 'fail/') return self.api.post(url=url, data=json.dumps({'reason': reason})) @function_log diff --git a/connect/resource/template.py b/connect/resource/template.py index 7b1469b..985aa75 100644 --- a/connect/resource/template.py +++ b/connect/resource/template.py @@ -7,7 +7,7 @@ from connect.models import ActivationTemplateResponse, ActivationTileResponse from .base import BaseResource -from .utils import joinurl +from .utils import join_url class TemplateResource(BaseResource): @@ -19,9 +19,9 @@ class TemplateResource(BaseResource): def render(self, pk, request_id): if not all([pk, request_id]): - raise ValueError('Invalid ids for render temlpate') + raise ValueError('Invalid ids for render template') - url = joinurl(self._obj_url(pk), 'render') + url = join_url(self._obj_url(pk), 'render') response = self.api.get(url, params={'request_id': request_id}) return ActivationTileResponse(response) diff --git a/connect/resource/utils.py b/connect/resource/utils.py index aa58823..aab6960 100644 --- a/connect/resource/utils.py +++ b/connect/resource/utils.py @@ -8,8 +8,8 @@ from requests.compat import urljoin -def joinurl(base, url, allow_fragments=True): +def join_url(base, url, allow_fragments=True): """ Method for the correct formation of the URL """ if base and isinstance(base, str): base += '/' if base[-1] != '/' else '' - return urljoin(base, url, allow_fragments=True) + return urljoin(base, url, allow_fragments=allow_fragments) diff --git a/example/example.py b/example/example.py index 7c459ac..701aa3c 100644 --- a/example/example.py +++ b/example/example.py @@ -16,16 +16,16 @@ class ExampleRequestProcessor(FulfillmentAutomation): - def process_request(self, request): + def process_request(self, req): # custom logic - if request.type == 'purchase': - for item in request.asset.items: + if req.type == 'purchase': + for item in req.asset.items: if item.quantity > 100000: raise FulfillmentFail( message='Is Not possible to purchase product') - for param in request.asset.params: + for param in req.asset.params: if param.name == 'email' and not param.value: param.value_error = 'Email address has not been provided, please provide one' raise FulfillmentInquire(params=[param]) @@ -34,14 +34,14 @@ def process_request(self, request): return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, you decided ' 'to have an account in our amazing service!') # or - # return TemplateResource().render(pk='TEMPLATE_ID', request_id=request.id) + # return TemplateResource().render(pk='TEMPLATE_ID', request_id=req.id) - # aprrove by Template - return ActivationTemplateResponse(template_id="TL-497-535-242") + # approve by Template + # return ActivationTemplateResponse(template_id="TL-497-535-242") # or # return TemplateResource().get(pk='TEMPLATE_ID') - elif request.type == 'change': + elif req.type == 'change': # fail raise FulfillmentFail() else: @@ -50,5 +50,5 @@ def process_request(self, request): if __name__ == '__main__': - request = ExampleRequestProcessor(Config(file='config.json')) + request = ExampleRequestProcessor(Config(filename='config.json')) request.process() diff --git a/setup.py b/setup.py index b2c3812..27a5f87 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,10 @@ from setuptools import find_packages, setup try: # for pip >= 10 + # noinspection PyProtectedMember,PyPackageRequirements from pip._internal.req import parse_requirements except ImportError: # for pip <= 9.0.3 + # noinspection PyPackageRequirements,PyUnresolvedReferences from pip.req import parse_requirements install_reqs = parse_requirements( @@ -20,7 +22,7 @@ dirname(abspath(__file__)), 'requirements', 'sdk.txt', - ), session='None') + ), session=None) VERSION = environ.get('TRAVIS_TAG') PACKAGES = find_packages(exclude=['tests*']) diff --git a/tests/test_config.py b/tests/test_config.py index b0963fa..7e3b7eb 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -30,11 +30,11 @@ def teardown_module(module): def test_init_config_with_non_existing_file(): with pytest.raises(IOError): - Config(file='non_existing_config.json') + Config(filename='non_existing_config.json') def test_init_config_with_file(): - _assert_config(Config(file='config.json')) + _assert_config(Config(filename='config.json')) def test_init_config_with_arguments(): @@ -55,14 +55,18 @@ def test_init_config_with_invalid_arguments(): def test_config_immutable_properties(): - config = Config(file='config.json') + config = Config(filename='config.json') with pytest.raises(AttributeError): + # noinspection PyPropertyAccess config.api_key = conf_dict.get('apiKey') with pytest.raises(AttributeError): + # noinspection PyPropertyAccess config.api_url = conf_dict.get('apiEndpoint') with pytest.raises(AttributeError): + # noinspection PyPropertyAccess config.products = [conf_dict.get('products')] + def _assert_config(config): assert config.api_key == conf_dict.get('apiKey') assert config.api_url == conf_dict.get('apiEndpoint') diff --git a/tests/test_models.py b/tests/test_models.py index aa4a022..8ce7e65 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -19,8 +19,8 @@ def _get_response_ok(): response.ok = True - with open(os.path.join(os.path.dirname(__file__), 'response.json')) as file: - response.content = file.read() + with open(os.path.join(os.path.dirname(__file__), 'response.json')) as file_handle: + response.content = file_handle.read() return response From 78e851565e5f8c040cf77e5b2847f4473cfdec74 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Mon, 4 Mar 2019 15:22:09 +0100 Subject: [PATCH 02/17] More code inspection warnings removed --- README.md | 23 ++++++++++++----------- connect/models/activation_response.py | 7 ++----- connect/models/base.py | 7 +++---- example/example.py | 23 ++++++++++++----------- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9066a13..113b8e4 100644 --- a/README.md +++ b/README.md @@ -40,17 +40,18 @@ $ pip install connect-sdk from connect import FulfillmentAutomation from connect.config import Config from connect.logger import logger +# noinspection PyUnresolvedReferences from connect.models import ActivationTemplateResponse, ActivationTileResponse from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip -# set logger level / default level ERROR +# Set logger level / default level ERROR logger.setLevel("DEBUG") class ExampleRequestProcessor(FulfillmentAutomation): def process_request(self, req): - # custom logic + # Custom logic if req.type == 'purchase': for item in req.asset.items: if item.quantity > 100000: @@ -62,16 +63,16 @@ class ExampleRequestProcessor(FulfillmentAutomation): param.value_error = 'Email address has not been provided, please provide one' raise FulfillmentInquire(params=[param]) - # approve by ActivationTile - return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, ' - 'you decided to have an account in our amazing service!') - # or - # return TemplateResource().render(pk='TEMPLATE_ID', request_id=req.id) + # Approve by ActivationTile + return ActivationTileResponse('\n # Welcome to Fallball!\n\nYes, ' + 'you decided to have an account in our amazing service!') + # Or + # return TemplateResource(self.config).render(pk='TEMPLATE_ID', request_id=req.id) - # approve by Template - # return ActivationTemplateResponse(template_id="TL-497-535-242") - # or - # return TemplateResource().get(pk='TEMPLATE_ID') + # Approve by Template + # return ActivationTemplateResponse("TL-497-535-242") + # Or + # return TemplateResource(self.config).get(pk='TEMPLATE_ID') elif req.type == 'change': # fail diff --git a/connect/models/activation_response.py b/connect/models/activation_response.py index 356c5b6..c8c59e0 100644 --- a/connect/models/activation_response.py +++ b/connect/models/activation_response.py @@ -9,15 +9,12 @@ class ActivationTileResponse(object): - tile = 'Activation succeeded' - - # noinspection PyUnusedLocal - def __init__(self, markdown=None, **kwargs): + def __init__(self, markdown=None): if markdown: try: self.tile = json.loads(markdown) except ValueError: - self.tile = markdown + self.tile = markdown or 'Activation succeeded' class ActivationTemplateResponse(object): diff --git a/connect/models/base.py b/connect/models/base.py index b1fda32..2367db1 100644 --- a/connect/models/base.py +++ b/connect/models/base.py @@ -10,10 +10,9 @@ class BaseModel: def __init__(self, **kwargs): - self.id = kwargs.get('id') - if kwargs: - for attr, val in kwargs.items(): - setattr(self, attr, val) + # Inject parsed properties in the model + for attr, val in kwargs.items(): + setattr(self, attr, val) class BaseSchema(Schema): diff --git a/example/example.py b/example/example.py index 701aa3c..cfde597 100644 --- a/example/example.py +++ b/example/example.py @@ -8,17 +8,18 @@ from connect import FulfillmentAutomation from connect.config import Config from connect.logger import logger +# noinspection PyUnresolvedReferences from connect.models import ActivationTemplateResponse, ActivationTileResponse from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip -# set logger level / default level ERROR +# Set logger level / default level ERROR logger.setLevel("DEBUG") class ExampleRequestProcessor(FulfillmentAutomation): def process_request(self, req): - # custom logic + # Custom logic if req.type == 'purchase': for item in req.asset.items: if item.quantity > 100000: @@ -30,16 +31,16 @@ def process_request(self, req): param.value_error = 'Email address has not been provided, please provide one' raise FulfillmentInquire(params=[param]) - # approve by ActivationTile - return ActivationTileResponse(tile='\n # Welcome to Fallball!\n\nYes, you decided ' - 'to have an account in our amazing service!') - # or - # return TemplateResource().render(pk='TEMPLATE_ID', request_id=req.id) + # Approve by ActivationTile + return ActivationTileResponse('\n # Welcome to Fallball!\n\nYes, you decided ' + 'to have an account in our amazing service!') + # Or + # return TemplateResource(self.config).render(pk='TEMPLATE_ID', request_id=req.id) - # approve by Template - # return ActivationTemplateResponse(template_id="TL-497-535-242") - # or - # return TemplateResource().get(pk='TEMPLATE_ID') + # Approve by Template + # return ActivationTemplateResponse('TL-497-535-242') + # Or + # return TemplateResource(self.config).get(pk='TEMPLATE_ID') elif req.type == 'change': # fail From aebe426aebe9f9c682fd6d40d665d3bcee8bd45b Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Mon, 4 Mar 2019 16:43:22 +0100 Subject: [PATCH 03/17] Added type hints for models --- connect/models/activation_response.py | 15 ++++++---- connect/models/asset.py | 20 +++++++++---- connect/models/base.py | 2 ++ connect/models/company.py | 2 +- connect/models/connection.py | 12 +++++--- connect/models/exception.py | 15 ++++++++-- connect/models/fulfillment.py | 15 ++++++++-- connect/models/hub.py | 13 +++++++-- connect/models/marketplace.py | 42 +++++++++++++++++++++++---- connect/models/parameters.py | 10 +++++-- connect/models/product.py | 7 +++-- connect/models/server_error.py | 5 +++- connect/models/tiers.py | 17 +++++++++-- connect/resource/base.py | 5 ++++ example/example.py | 3 ++ 15 files changed, 147 insertions(+), 36 deletions(-) diff --git a/connect/models/activation_response.py b/connect/models/activation_response.py index c8c59e0..5478351 100644 --- a/connect/models/activation_response.py +++ b/connect/models/activation_response.py @@ -9,14 +9,19 @@ class ActivationTileResponse(object): + tile = None # type: str + def __init__(self, markdown=None): - if markdown: - try: - self.tile = json.loads(markdown) - except ValueError: - self.tile = markdown or 'Activation succeeded' + # type: (str) -> None + try: + self.tile = json.loads(markdown) + except ValueError: + self.tile = markdown or 'Activation succeeded' class ActivationTemplateResponse(object): + template_id = None # type: str + def __init__(self, template_id): + # type: (str) -> None self.template_id = template_id diff --git a/connect/models/asset.py b/connect/models/asset.py index 1f44f61..16b37a3 100644 --- a/connect/models/asset.py +++ b/connect/models/asset.py @@ -6,16 +6,24 @@ """ from marshmallow import fields, post_load +from typing import List from .base import BaseModel, BaseSchema -from .connection import ConnectionSchema -from .parameters import ParamSchema -from .product import ItemSchema, ProductSchema -from .tiers import TiersSchemaMixin +from .connection import Connection, ConnectionSchema +from .parameters import Param, ParamSchema +from .product import Item, ItemSchema, Product, ProductSchema +from .tiers import TiersMixin, TiersMixinSchema class Asset(BaseModel): - pass + status = None # type: str + external_id = None # type: str + external_uid = None # type: str + product = None # type: Product + connection = None # type: Connection + items = None # type: List[Item] + params = None # type: List[Param] + tiers = None # type: List[TiersMixin] class AssetSchema(BaseSchema): @@ -28,7 +36,7 @@ class AssetSchema(BaseSchema): ) items = fields.List(fields.Nested(ItemSchema)) params = fields.List(fields.Nested(ParamSchema)) - tiers = fields.Nested(TiersSchemaMixin) + tiers = fields.Nested(TiersMixinSchema) @post_load def make_object(self, data): diff --git a/connect/models/base.py b/connect/models/base.py index 2367db1..0a191a3 100644 --- a/connect/models/base.py +++ b/connect/models/base.py @@ -9,6 +9,8 @@ class BaseModel: + id = None # type: str + def __init__(self, **kwargs): # Inject parsed properties in the model for attr, val in kwargs.items(): diff --git a/connect/models/company.py b/connect/models/company.py index 8f3d84a..25ac4cd 100644 --- a/connect/models/company.py +++ b/connect/models/company.py @@ -11,7 +11,7 @@ class Company(BaseModel): - pass + name = None # type: str class CompanySchema(BaseSchema): diff --git a/connect/models/connection.py b/connect/models/connection.py index 5f63a6b..2721b1e 100644 --- a/connect/models/connection.py +++ b/connect/models/connection.py @@ -8,13 +8,17 @@ from marshmallow import fields, post_load from .base import BaseModel, BaseSchema -from .company import CompanySchema -from .hub import HubSchema -from .product import ProductSchema +from .company import Company, CompanySchema +from .hub import Hub, HubSchema +from .product import Product, ProductSchema class Connection(BaseModel): - pass + type = None # type: str + provider = None # type: Company + vendor = None # type: Company + product = None # type: Product + hub = None # type: Hub class ConnectionSchema(BaseSchema): diff --git a/connect/models/exception.py b/connect/models/exception.py index cbfe47f..9f1e6b3 100644 --- a/connect/models/exception.py +++ b/connect/models/exception.py @@ -4,12 +4,17 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ +from typing import List from .server_error import ServerError class Message(Exception): + code = None # type: str + obj = None # type: object + def __init__(self, message='', code='', obj=None): + # type: (str, str, object) -> None self.message = message self.code = code self.obj = obj @@ -17,13 +22,17 @@ def __init__(self, message='', code='', obj=None): class FulfillmentFail(Message): def __init__(self, *args, **kwargs): + # type: (*any, **any) -> None super(FulfillmentFail, self).__init__(*args, **kwargs) self.code = 'fail' self.message = self.message or 'Request failed' class FulfillmentInquire(Message): + params = None # type: List[str] + def __init__(self, *args, **kwargs): + # type: (*any, **any) -> None super(FulfillmentInquire, self).__init__(*args, **kwargs) self.message = self.message or 'Correct user input required' self.params = kwargs.get('params', []) @@ -32,18 +41,20 @@ def __init__(self, *args, **kwargs): class Skip(Message): def __init__(self): + # type: () -> None self.code = 'skip' class ServerErrorException(Exception): - message = 'Server error' - def __init__(self, error=None, *args, **kwargs): + # type: (ServerError, *any, **any) -> None if error and isinstance(error, ServerError): self.message = str({ "error_code": error.error_code, "params": kwargs.get('params', []), "errors": error.errors, }) + else: + self.message = 'Server error' super(ServerErrorException, self).__init__(self.message, *args) diff --git a/connect/models/fulfillment.py b/connect/models/fulfillment.py index 503168b..d42f735 100644 --- a/connect/models/fulfillment.py +++ b/connect/models/fulfillment.py @@ -7,13 +7,22 @@ from marshmallow import fields, post_load -from .asset import AssetSchema +from .asset import Asset, AssetSchema from .base import BaseModel, BaseSchema -from .marketplace import ContractSchema, MarketplaceSchema +from .marketplace import Contract, ContractSchema, Marketplace, MarketplaceSchema class Fulfillment(BaseModel): - pass + activation_key = None # type: str + asset = None # type: Asset + status = None # type: str + type = None # type: str + updated = None # type: str + created = None # type: str + reason = None # type: str + params_from_url = None # type: str + contract = None # type: Contract + marketplace = None # type: Marketplace class FulfillmentSchema(BaseSchema): diff --git a/connect/models/hub.py b/connect/models/hub.py index cfec109..6cb0128 100644 --- a/connect/models/hub.py +++ b/connect/models/hub.py @@ -11,7 +11,7 @@ class Hub(BaseModel): - pass + name = None # type: str class HubSchema(BaseSchema): @@ -22,6 +22,15 @@ def make_object(self, data): return Hub(**data) -class HubsSchemaMixin(Schema): +class HubsMixin(BaseModel): + hub = None # type: Hub + external_id = None # type: str + + +class HubsMixinSchema(Schema): hub = fields.Nested(HubSchema, only=('id', 'name')) external_id = fields.Str() + + @post_load + def make_object(self, data): + return HubsMixin(**data) diff --git a/connect/models/marketplace.py b/connect/models/marketplace.py index 422bf9f..0527d05 100644 --- a/connect/models/marketplace.py +++ b/connect/models/marketplace.py @@ -8,12 +8,18 @@ from marshmallow import fields, post_load from .base import BaseModel, BaseSchema -from .company import CompanySchema -from .hub import HubsSchemaMixin +from .company import Company, CompanySchema +from .hub import HubsMixin, HubsMixinSchema class Marketplace(BaseModel): - pass + name = None # type: str + zone = None # type: str + description = None # type: str + active_contract = None # type: int + icon = None # type: str + owner = None # type: Company + hubs = None # type: HubsMixin class MarketplaceSchema(BaseSchema): @@ -23,7 +29,7 @@ class MarketplaceSchema(BaseSchema): active_contract = fields.Int() icon = fields.Str() owner = fields.Nested(CompanySchema, only=('id', 'name')) - hubs = fields.List(fields.Nested(HubsSchemaMixin, only=('id', 'name'))) + hubs = fields.List(fields.Nested(HubsMixinSchema, only=('id', 'name'))) @post_load def make_object(self, data): @@ -31,7 +37,18 @@ def make_object(self, data): class Agreement(BaseModel): - pass + type = None # type: str + title = None # type: str + description = None # type: str + created = None # type: str + updated = None # type: str + owner = None # type: Company + stats = None # type: dict + active = None # type: bool + version = None # type: int + link = None # type: str + version_created = None # type: str + version_contracts = None # type: int class AgreementSchema(BaseSchema): @@ -54,7 +71,20 @@ def make_object(self, data): class Contract(BaseModel): - pass + name = None # type: str + status = None # type: str + version = None # type: int + type = None # type: str + agreement = None # type: Agreement + marketplace = None # type: Marketplace + owner = None # type: Company + creater = None # type: Company + created = None # type: str + updated = None # type: str + enrolled = None # type: str + version_created = None # type: str + activation = None # type: dict + signee = None # type: Company class ContractSchema(BaseSchema): diff --git a/connect/models/parameters.py b/connect/models/parameters.py index 0633680..3a990f5 100644 --- a/connect/models/parameters.py +++ b/connect/models/parameters.py @@ -6,12 +6,14 @@ """ from marshmallow import Schema, fields, post_load +from typing import List from .base import BaseModel, BaseSchema class ValueChoice(BaseModel): - pass + value = None # type: str + label = None # type: str class ValueChoiceSchema(Schema): @@ -24,7 +26,11 @@ def make_object(self, data): class Param(BaseModel): - pass + name = None # type: str + type = None # type: str + value = None # type: str + value_choices = None # type: List[ValueChoice] + value_error = None # type: str class ParamSchema(BaseSchema): diff --git a/connect/models/product.py b/connect/models/product.py index c4e331a..3321ab2 100644 --- a/connect/models/product.py +++ b/connect/models/product.py @@ -11,7 +11,7 @@ class Product(BaseModel): - pass + name = None # type: str class ProductSchema(BaseSchema): @@ -23,7 +23,10 @@ def make_object(self, data): class Item(BaseModel): - pass + global_id = None # type: str + mpn = None # type: str + old_quantity = None # type: str + quantity = None # type: int class ItemSchema(BaseSchema): diff --git a/connect/models/server_error.py b/connect/models/server_error.py index 5795c5d..c29bfb9 100644 --- a/connect/models/server_error.py +++ b/connect/models/server_error.py @@ -6,12 +6,15 @@ """ from marshmallow import Schema, fields, post_load +from typing import List from .base import BaseModel class ServerError(BaseModel): - pass + error_code = None # type: str + params = None # type: dict + errors = None # type: List[str] class ServerErrorSchema(Schema): diff --git a/connect/models/tiers.py b/connect/models/tiers.py index 825ffb3..adeeea3 100644 --- a/connect/models/tiers.py +++ b/connect/models/tiers.py @@ -11,7 +11,10 @@ class Tier(BaseModel): - pass + name = None # type: str + contract_info = None # type: dict + external_id = None # type: str + external_uid = None # type: str class TierSchema(BaseSchema): @@ -25,7 +28,17 @@ def make_object(self, data): return Tier(**data) -class TiersSchemaMixin(Schema): +class TiersMixin(BaseModel): + customer = None # type: Tier + tier1 = None # type: Tier + tier2 = None # type: Tier + + +class TiersMixinSchema(Schema): customer = fields.Nested(TierSchema) tier1 = fields.Nested(TierSchema) tier2 = fields.Nested(TierSchema) + + @post_load + def make_object(self, data): + return TiersMixin(**data) diff --git a/connect/resource/base.py b/connect/resource/base.py index 57f032e..717f3da 100644 --- a/connect/resource/base.py +++ b/connect/resource/base.py @@ -6,6 +6,7 @@ """ import requests +from requests import Response from connect.config import Config from connect.logger import function_log, logger @@ -15,14 +16,17 @@ class ApiClient(object): + config = None # type: Config def __init__(self, config): + # type: (Config) -> None if not isinstance(config, Config): raise ValueError('A valid Config object is required to create an ApiClient') self.config = config @property def headers(self): + # type: () -> dict return { "Authorization": self.config.api_key, "Content-Type": "application/json", @@ -30,6 +34,7 @@ def headers(self): @staticmethod def check_response(response): + # type: (Response) -> str if not hasattr(response, 'content'): raise AttributeError( 'Response not attribute content. Check your request params' diff --git a/example/example.py b/example/example.py index cfde597..dc715fe 100644 --- a/example/example.py +++ b/example/example.py @@ -11,13 +11,16 @@ # noinspection PyUnresolvedReferences from connect.models import ActivationTemplateResponse, ActivationTileResponse from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip +from connect.models.fulfillment import Fulfillment # Set logger level / default level ERROR + logger.setLevel("DEBUG") class ExampleRequestProcessor(FulfillmentAutomation): def process_request(self, req): + # type: (Fulfillment) -> object # Custom logic if req.type == 'purchase': From 06c7f9d9b07d8ad8e3b2c37a834940a104dedfeb Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 5 Mar 2019 10:45:02 +0100 Subject: [PATCH 04/17] Corrected syntax for parse_requirements --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 27a5f87..de194d2 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ dirname(abspath(__file__)), 'requirements', 'sdk.txt', - ), session=None) + ), session='None') VERSION = environ.get('TRAVIS_TAG') PACKAGES = find_packages(exclude=['tests*']) From e59d6ade63e446166c9277a9f299f8875d2731b8 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 5 Mar 2019 11:05:29 +0100 Subject: [PATCH 05/17] Skip exception can now receive arguments --- connect/models/exception.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/connect/models/exception.py b/connect/models/exception.py index 9f1e6b3..4216603 100644 --- a/connect/models/exception.py +++ b/connect/models/exception.py @@ -24,8 +24,8 @@ class FulfillmentFail(Message): def __init__(self, *args, **kwargs): # type: (*any, **any) -> None super(FulfillmentFail, self).__init__(*args, **kwargs) - self.code = 'fail' self.message = self.message or 'Request failed' + self.code = 'fail' class FulfillmentInquire(Message): @@ -35,13 +35,15 @@ def __init__(self, *args, **kwargs): # type: (*any, **any) -> None super(FulfillmentInquire, self).__init__(*args, **kwargs) self.message = self.message or 'Correct user input required' - self.params = kwargs.get('params', []) self.code = 'inquire' + self.params = kwargs.get('params', []) class Skip(Message): - def __init__(self): - # type: () -> None + def __init__(self, *args, **kwargs): + # type: (*any, **any) -> None + super(Skip, self).__init__(*args, **kwargs) + self.message = self.message or 'Request skipped' self.code = 'skip' From 6984e25e23061a46db47f4e46ab21f7f31f94c24 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 5 Mar 2019 11:13:39 +0100 Subject: [PATCH 06/17] Fixed Asset.tiers type --- connect/models/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connect/models/asset.py b/connect/models/asset.py index 16b37a3..af4f0ce 100644 --- a/connect/models/asset.py +++ b/connect/models/asset.py @@ -23,7 +23,7 @@ class Asset(BaseModel): connection = None # type: Connection items = None # type: List[Item] params = None # type: List[Param] - tiers = None # type: List[TiersMixin] + tiers = None # type: TiersMixin class AssetSchema(BaseSchema): From 96ba27f7088a1bdbfd5f9559b59467b4bb55a4bd Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 5 Mar 2019 13:29:03 +0100 Subject: [PATCH 07/17] Fixed typo in Tier model --- connect/models/tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connect/models/tiers.py b/connect/models/tiers.py index adeeea3..e51f74e 100644 --- a/connect/models/tiers.py +++ b/connect/models/tiers.py @@ -12,7 +12,7 @@ class Tier(BaseModel): name = None # type: str - contract_info = None # type: dict + contact_info = None # type: dict external_id = None # type: str external_uid = None # type: str From 13b070ee4987712453ff064540c577232c7da3ba Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 5 Mar 2019 15:25:35 +0100 Subject: [PATCH 08/17] Added type hints for Contact, ContactInfo and Phonenumber --- connect/models/contact.py | 18 +++++++++++++++--- connect/models/tiers.py | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/connect/models/contact.py b/connect/models/contact.py index 92697ff..50cf3a7 100644 --- a/connect/models/contact.py +++ b/connect/models/contact.py @@ -11,7 +11,10 @@ class PhoneNumber(BaseModel): - pass + country_code = None # type: str + area_code = None # type: str + phone_number = None # type: str + extension = None # type: str class PhoneNumberSchema(BaseSchema): @@ -26,7 +29,10 @@ def make_object(self, data): class Contact(BaseModel): - pass + email = None # type: str + first_name = None # type: str + last_name = None # type: str + phone_number = None # type: PhoneNumber class ContactSchema(BaseSchema): @@ -41,7 +47,13 @@ def make_object(self, data): class ContactInfo(BaseModel): - pass + address_line1 = None # type: str + address_line2 = None # type: str + city = None # type: str + contact = None # type: Contact + country = None # type: str + postal_code = None # type: str + state = None # type: str class ContactInfoSchema(BaseSchema): diff --git a/connect/models/tiers.py b/connect/models/tiers.py index 53c05c6..555d55a 100644 --- a/connect/models/tiers.py +++ b/connect/models/tiers.py @@ -8,12 +8,12 @@ from marshmallow import Schema, fields, post_load from .base import BaseModel, BaseSchema -from .contact import ContactInfoSchema +from .contact import ContactInfo, ContactInfoSchema class Tier(BaseModel): name = None # type: str - contact_info = None # type: dict + contact_info = None # type: ContactInfo external_id = None # type: str external_uid = None # type: str From f1650ee47ef8b03584c7eb779481a707c03d91cf Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Wed, 6 Mar 2019 10:50:41 +0100 Subject: [PATCH 09/17] Added Item and ItemSchema to exported classes by models package --- connect/models/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connect/models/__init__.py b/connect/models/__init__.py index f97ff6e..62fc8c7 100644 --- a/connect/models/__init__.py +++ b/connect/models/__init__.py @@ -9,6 +9,7 @@ from .base import BaseSchema from .fulfillment import FulfillmentSchema from .parameters import Param, ParamSchema +from .product import Item, ItemSchema from .server_error import ServerErrorSchema __all__ = [ @@ -16,7 +17,9 @@ 'ActivationTileResponse', 'BaseSchema', 'FulfillmentSchema', - 'ServerErrorSchema', + 'Item', + 'ItemSchema', 'Param', 'ParamSchema', + 'ServerErrorSchema', ] From 45de30a569fdbd466959c5c2090d7f017c89675a Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Thu, 7 Mar 2019 17:29:47 +0100 Subject: [PATCH 10/17] Added typing to Config.get_instance() --- connect/config.py | 1 + tests/test_config.py | 16 +--------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/connect/config.py b/connect/config.py index 6054f69..c644cdf 100644 --- a/connect/config.py +++ b/connect/config.py @@ -71,6 +71,7 @@ def __init__( @classmethod def get_instance(cls): + # type: () -> Config if not cls._instance: cls._instance = Config(filename='config.json') return cls._instance diff --git a/tests/test_config.py b/tests/test_config.py index 7af6161..17695bf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -37,6 +37,7 @@ def test_global_implicit_global_config(): assert Config.get_instance().products[0] == conf_dict.get('products') +# noinspection PyPropertyAccess def test_global_config_immutable_properties(): with pytest.raises(AttributeError): Config.get_instance().api_key = conf_dict.get('apiKey') @@ -79,21 +80,6 @@ def test_config_immutable_properties(): config.products = [conf_dict.get('products')] -def test_global_config(): - Config.instance = None # Reset global config - Config(filename='config.json') - _assert_global_config() - - -# noinspection PyPropertyAccess -def test_global_config_immutable_properties(): - Config(filename='config.json') - with pytest.raises(AttributeError): - Config.instance.api_key = conf_dict.get('apiKey') - Config.instance.api_url = conf_dict.get('apiEndpoint') - Config.instance.products = [conf_dict.get('products')] - - def _assert_config(config): assert config.api_key == conf_dict.get('apiKey') assert config.api_url == conf_dict.get('apiEndpoint') From dfdc93283f7a94f41b1bb6e274717827fb7f7d6b Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Mon, 11 Mar 2019 11:08:36 +0100 Subject: [PATCH 11/17] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4577d42..a8f4393 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ class ExampleRequestProcessor(FulfillmentAutomation): # return TemplateResource().render(pk='TEMPLATE_ID', request_id=req.id) # Approve by Template - # return ActivationTemplateResponse("TL-497-535-242") + return ActivationTemplateResponse("TL-497-535-242") # Or # return TemplateResource().get(pk='TEMPLATE_ID') From 57376839cf8558a770f9460233172540c90db89c Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Wed, 13 Mar 2019 09:49:35 +0100 Subject: [PATCH 12/17] Update asset.py --- connect/models/asset.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/connect/models/asset.py b/connect/models/asset.py index 617e0be..7c1ab19 100644 --- a/connect/models/asset.py +++ b/connect/models/asset.py @@ -25,17 +25,14 @@ class Asset(BaseModel): params = None # type: List[Param] tiers = None # type: TiersMixin - def get_parameter_by_id(self, id_): def get_param_by_id(self, id_): try: - # noinspection PyUnresolvedReferences return list(filter(lambda param: param.id == id_, self.params))[0] except IndexError: return None def get_item_by_mpn(self, mpn): try: - # noinspection PyUnresolvedReferences return list(filter(lambda item: item.mpn == mpn, self.items))[0] except IndexError: return None From c86bcd8196335781a0e723a926fd6ed391e2519a Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Wed, 13 Mar 2019 12:41:00 +0100 Subject: [PATCH 13/17] Renamed TiersMixin to Tiers and HubsMixin to Hubs --- connect/models/asset.py | 6 +++--- connect/models/hub.py | 6 +++--- connect/models/marketplace.py | 6 +++--- connect/models/tiers.py | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/connect/models/asset.py b/connect/models/asset.py index 7c1ab19..b31e450 100644 --- a/connect/models/asset.py +++ b/connect/models/asset.py @@ -12,7 +12,7 @@ from .connection import Connection, ConnectionSchema from .parameters import Param, ParamSchema from .product import Item, ItemSchema, Product, ProductSchema -from .tiers import TiersMixin, TiersMixinSchema +from .tiers import Tiers, TiersSchema class Asset(BaseModel): @@ -23,7 +23,7 @@ class Asset(BaseModel): connection = None # type: Connection items = None # type: List[Item] params = None # type: List[Param] - tiers = None # type: TiersMixin + tiers = None # type: Tiers def get_param_by_id(self, id_): try: @@ -48,7 +48,7 @@ class AssetSchema(BaseSchema): ) items = fields.List(fields.Nested(ItemSchema)) params = fields.List(fields.Nested(ParamSchema)) - tiers = fields.Nested(TiersMixinSchema) + tiers = fields.Nested(TiersSchema) @post_load def make_object(self, data): diff --git a/connect/models/hub.py b/connect/models/hub.py index 6cb0128..485952f 100644 --- a/connect/models/hub.py +++ b/connect/models/hub.py @@ -22,15 +22,15 @@ def make_object(self, data): return Hub(**data) -class HubsMixin(BaseModel): +class Hubs(BaseModel): hub = None # type: Hub external_id = None # type: str -class HubsMixinSchema(Schema): +class HubsSchema(Schema): hub = fields.Nested(HubSchema, only=('id', 'name')) external_id = fields.Str() @post_load def make_object(self, data): - return HubsMixin(**data) + return Hubs(**data) diff --git a/connect/models/marketplace.py b/connect/models/marketplace.py index 0527d05..ed462ea 100644 --- a/connect/models/marketplace.py +++ b/connect/models/marketplace.py @@ -9,7 +9,7 @@ from .base import BaseModel, BaseSchema from .company import Company, CompanySchema -from .hub import HubsMixin, HubsMixinSchema +from .hub import Hubs, HubsSchema class Marketplace(BaseModel): @@ -19,7 +19,7 @@ class Marketplace(BaseModel): active_contract = None # type: int icon = None # type: str owner = None # type: Company - hubs = None # type: HubsMixin + hubs = None # type: Hubs class MarketplaceSchema(BaseSchema): @@ -29,7 +29,7 @@ class MarketplaceSchema(BaseSchema): active_contract = fields.Int() icon = fields.Str() owner = fields.Nested(CompanySchema, only=('id', 'name')) - hubs = fields.List(fields.Nested(HubsMixinSchema, only=('id', 'name'))) + hubs = fields.List(fields.Nested(HubsSchema, only=('id', 'name'))) @post_load def make_object(self, data): diff --git a/connect/models/tiers.py b/connect/models/tiers.py index 555d55a..d9c5ed5 100644 --- a/connect/models/tiers.py +++ b/connect/models/tiers.py @@ -29,17 +29,17 @@ def make_object(self, data): return Tier(**data) -class TiersMixin(BaseModel): +class Tiers(BaseModel): customer = None # type: Tier tier1 = None # type: Tier tier2 = None # type: Tier -class TiersMixinSchema(Schema): +class TiersSchema(Schema): customer = fields.Nested(TierSchema) tier1 = fields.Nested(TierSchema) tier2 = fields.Nested(TierSchema) @post_load def make_object(self, data): - return TiersMixin(**data) + return Tiers(**data) From 79e745c031a967dc55dd464ba261e62b0c9781f7 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Wed, 13 Mar 2019 16:02:15 +0100 Subject: [PATCH 14/17] More type hints --- connect/config.py | 5 +++++ connect/resource/base.py | 23 ++++++++++++++++++---- connect/resource/fulfillment.py | 8 +++++++- connect/resource/fulfillment_automation.py | 11 ++++++++--- connect/resource/template.py | 4 ++++ connect/resource/utils.py | 1 + 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/connect/config.py b/connect/config.py index 1c4ccf6..de608e7 100644 --- a/connect/config.py +++ b/connect/config.py @@ -20,6 +20,8 @@ def __init__( products=None, filename=None ): + # type: (str, str, str, str) -> None + """ initialization config for public api :param api_url: Public api url @@ -78,12 +80,15 @@ def get_instance(cls): @property def api_url(self): + # type: () -> str return self._api_url @property def api_key(self): + # type: () -> str return self._api_key @property def products(self): + # type: () -> str return self._products diff --git a/connect/resource/base.py b/connect/resource/base.py index f7fe718..ecf025a 100644 --- a/connect/resource/base.py +++ b/connect/resource/base.py @@ -6,6 +6,7 @@ """ import requests +from typing import Any from connect.config import Config from connect.logger import function_log, logger @@ -15,18 +16,21 @@ class ApiClient(object): - config = None # type: Config - def __init__(self, config=None): # type: (Config) -> None # Assign passed config or globally configured instance - self.config = config or Config.get_instance() + self._config = config or Config.get_instance() # Assert data if not isinstance(self.config, Config): raise ValueError('A valid Config object is required to create an ApiClient') + @property + def config(self): + # type: () -> Config + return self._config + @property def headers(self): # type: () -> dict @@ -78,7 +82,7 @@ class BaseResource(object): def __init__(self, config=None): # Assign passed config or globally configured instance - self.config = config or Config.get_instance() + self._config = config or Config.get_instance() # Assert data if not self.__class__.resource: @@ -90,7 +94,13 @@ def __init__(self, config=None): if not BaseResource.api: BaseResource.api = ApiClient(config) + @property + def config(self): + # type: () -> Config + return self._config + def build_filter(self): + # type: () -> dict res_filter = {} if self.limit: res_filter['limit'] = self.limit @@ -99,12 +109,15 @@ def build_filter(self): @property def _list_url(self): + # type: () -> str return join_url(self.config.api_url, self.__class__.resource) def _obj_url(self, pk): + # type: (str) -> str return join_url(self._list_url, pk) def __loads_schema(self, response): + # type: (str) -> Any objects, error = self.schema.loads(response, many=True) if error: raise TypeError( @@ -115,12 +128,14 @@ def __loads_schema(self, response): return objects def get(self, pk): + # type: (str) -> Any response = self.api.get(url=self._obj_url(pk)) objects = self.__loads_schema(response) if isinstance(objects, list) and len(objects) > 0: return objects[0] def list(self): + # type: () -> Any filters = self.build_filter() logger.info('Get list request by filter - {}'.format(filters)) response = self.api.get(url=self._list_url, params=filters) diff --git a/connect/resource/fulfillment.py b/connect/resource/fulfillment.py index 1709bac..ff0da07 100644 --- a/connect/resource/fulfillment.py +++ b/connect/resource/fulfillment.py @@ -8,7 +8,7 @@ import json from connect.logger import function_log -from connect.models import FulfillmentSchema, Param +from connect.models import FulfillmentSchema, Param, ActivationTileResponse from .base import BaseResource from .template import TemplateResource from .utils import join_url @@ -20,6 +20,7 @@ class FulfillmentResource(BaseResource): schema = FulfillmentSchema() def build_filter(self): + # type: () -> dict filters = super(FulfillmentResource, self).build_filter() if self.config.products: filters['product_id'] = self.config.products @@ -29,24 +30,29 @@ def build_filter(self): @function_log def approve(self, pk, data): + # type: (str, dict) -> str url = join_url(self._obj_url(pk), 'approve/') return self.api.post(url=url, data=json.dumps(data if data else {})) @function_log def inquire(self, pk): + # type: (str) -> str return self.api.post(url=join_url(self._obj_url(pk), 'inquire/'), data=json.dumps({})) @function_log def fail(self, pk, reason): + # type: (str, str) -> str url = join_url(self._obj_url(pk), 'fail/') return self.api.post(url=url, data=json.dumps({'reason': reason})) @function_log def render_template(self, pk, template_id): + # type: (str, str) -> ActivationTileResponse return TemplateResource(self.config).render(template_id, pk) @function_log def update_parameters(self, pk, params): + # type: (str, list) -> str list_dict = [] for _ in params: list_dict.append(_.__dict__ if isinstance(_, Param) else _) diff --git a/connect/resource/fulfillment_automation.py b/connect/resource/fulfillment_automation.py index dec637a..95c5556 100644 --- a/connect/resource/fulfillment_automation.py +++ b/connect/resource/fulfillment_automation.py @@ -4,20 +4,24 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ +from typing import Any from connect.logger import logger from connect.models import ActivationTemplateResponse, ActivationTileResponse from connect.models.exception import FulfillmentFail, FulfillmentInquire, Skip +from connect.models.fulfillment import Fulfillment from .fulfillment import FulfillmentResource class FulfillmentAutomation(FulfillmentResource): def process(self): + # type: () -> Any for _ in self.list(): - self.dispatch(_) + return self.dispatch(_) def dispatch(self, request): + # type: (Fulfillment) -> Any try: logger.info('Start request process / ID request - {}'.format(request.id)) result = self.process_request(request) @@ -43,8 +47,9 @@ def dispatch(self, request): except Skip as skip: return skip.code + return def process_request(self, request): - raise NotImplementedError( - 'Please implementation `process_request` logic') + # type: (Fulfillment) -> Any + raise NotImplementedError('Please implement `process_request` logic') diff --git a/connect/resource/template.py b/connect/resource/template.py index 985aa75..45df821 100644 --- a/connect/resource/template.py +++ b/connect/resource/template.py @@ -4,6 +4,7 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ +from typing import Any from connect.models import ActivationTemplateResponse, ActivationTileResponse from .base import BaseResource @@ -18,6 +19,7 @@ class TemplateResource(BaseResource): resource = 'templates' def render(self, pk, request_id): + # type: (str, str) -> ActivationTileResponse if not all([pk, request_id]): raise ValueError('Invalid ids for render template') @@ -27,7 +29,9 @@ def render(self, pk, request_id): return ActivationTileResponse(response) def get(self, pk): + # type: (str) -> ActivationTemplateResponse return ActivationTemplateResponse(template_id=pk) def list(self): + # type: () -> Any raise AttributeError('This resource do not have method `list`') diff --git a/connect/resource/utils.py b/connect/resource/utils.py index aab6960..947fead 100644 --- a/connect/resource/utils.py +++ b/connect/resource/utils.py @@ -9,6 +9,7 @@ def join_url(base, url, allow_fragments=True): + # type: (str, str, bool) -> str """ Method for the correct formation of the URL """ if base and isinstance(base, str): base += '/' if base[-1] != '/' else '' From d7b1aaccb962ad759f151a515bffb1ea87115af5 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Thu, 14 Mar 2019 15:46:33 +0100 Subject: [PATCH 15/17] Changed type of Config.products to List[str] --- connect/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/connect/config.py b/connect/config.py index de608e7..d18e8b4 100644 --- a/connect/config.py +++ b/connect/config.py @@ -8,6 +8,8 @@ import json import os +from typing import List, Union + class Config(object): # Global instance @@ -20,7 +22,7 @@ def __init__( products=None, filename=None ): - # type: (str, str, str, str) -> None + # type: (str, str, Union[str, List[str]], str) -> None """ initialization config for public api @@ -90,5 +92,5 @@ def api_key(self): @property def products(self): - # type: () -> str + # type: () -> List[str] return self._products From 1a88b92d3486313f302b79fa168dfd91864010ae Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Tue, 19 Mar 2019 11:39:00 +0100 Subject: [PATCH 16/17] Fixes for flake8 validation --- connect/models/exception.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connect/models/exception.py b/connect/models/exception.py index e510b80..843972b 100644 --- a/connect/models/exception.py +++ b/connect/models/exception.py @@ -49,10 +49,10 @@ def __init__(self, *args, **kwargs): class ServerErrorException(Exception): message = 'Server error' # type: str - + def __init__(self, error=None, *args, **kwargs): # type: (ServerError, *any, **any) -> None - + if error and isinstance(error, ServerError): self.message = str({ "error_code": error.error_code, From e4faeb4672eaf80cc229ab3b63163d8d4460f6c1 Mon Sep 17 00:00:00 2001 From: Javier San Juan Cervera Date: Fri, 22 Mar 2019 14:37:56 +0100 Subject: [PATCH 17/17] Updated with Vladimir's proposals (and fixed an important bug I missed out on FulfillmentAutomation.process!) --- connect/config.py | 15 ++++----- connect/models/exception.py | 3 +- connect/resource/base.py | 38 ++++++++++++---------- connect/resource/fulfillment.py | 4 ++- connect/resource/fulfillment_automation.py | 15 ++++----- connect/resource/template.py | 11 ++++--- example/example.py | 3 +- tests/test_models.py | 6 ++-- 8 files changed, 49 insertions(+), 46 deletions(-) diff --git a/connect/config.py b/connect/config.py index 303adb0..e37dd0b 100644 --- a/connect/config.py +++ b/connect/config.py @@ -18,20 +18,19 @@ class Config(object): # noinspection PyShadowingBuiltins def __init__( self, - api_url=None, - api_key=None, - products=None, - file=None + api_url=None, # type: str + api_key=None, # type: str + products=None, # type: Union[str, List[str]] + file=None # type: str ): - # type: (str, str, Union[str, List[str]], str) -> None - """ - initialization config for public api + Initialization config for public api :param api_url: Public api url :param api_key: Service user ApiKey :param products (optional): Id products - :param file: Config file path + :param file: Config file name """ + # Check arguments if not file and not any([api_key, api_url]): raise ValueError('Filename or api_key and api_url are expected' diff --git a/connect/models/exception.py b/connect/models/exception.py index 843972b..ffcba81 100644 --- a/connect/models/exception.py +++ b/connect/models/exception.py @@ -6,6 +6,7 @@ """ from typing import List +from connect.models import Param from .server_error import ServerError @@ -29,7 +30,7 @@ def __init__(self, *args, **kwargs): class FulfillmentInquire(Message): - params = None # type: List[str] + params = None # type: List[Param] def __init__(self, *args, **kwargs): # type: (*any, **any) -> None diff --git a/connect/resource/base.py b/connect/resource/base.py index ecf025a..ee75f88 100644 --- a/connect/resource/base.py +++ b/connect/resource/base.py @@ -6,11 +6,12 @@ """ import requests -from typing import Any +from typing import Any, List, Dict from connect.config import Config from connect.logger import function_log, logger from connect.models import BaseSchema, ServerErrorSchema +from connect.models.base import BaseModel from connect.models.exception import ServerErrorException from .utils import join_url @@ -33,7 +34,7 @@ def config(self): @property def headers(self): - # type: () -> dict + # type: () -> Dict[str, str] return { "Authorization": self.config.api_key, "Content-Type": "application/json", @@ -99,8 +100,23 @@ def config(self): # type: () -> Config return self._config + @property + def list(self): + # type: () -> List[Any] + filters = self.build_filter() + logger.info('Get list request by filter - {}'.format(filters)) + response = self.api.get(url=self._list_url, params=filters) + return self.__loads_schema(response) + + def get(self, pk): + # type: (str) -> Any + response = self.api.get(url=self._obj_url(pk)) + objects = self.__loads_schema(response) + if isinstance(objects, list) and len(objects) > 0: + return objects[0] + def build_filter(self): - # type: () -> dict + # type: () -> Dict[str, Any] res_filter = {} if self.limit: res_filter['limit'] = self.limit @@ -117,7 +133,7 @@ def _obj_url(self, pk): return join_url(self._list_url, pk) def __loads_schema(self, response): - # type: (str) -> Any + # type: (str) -> List[BaseModel] objects, error = self.schema.loads(response, many=True) if error: raise TypeError( @@ -126,17 +142,3 @@ def __loads_schema(self, response): ) return objects - - def get(self, pk): - # type: (str) -> Any - response = self.api.get(url=self._obj_url(pk)) - objects = self.__loads_schema(response) - if isinstance(objects, list) and len(objects) > 0: - return objects[0] - - def list(self): - # type: () -> Any - filters = self.build_filter() - logger.info('Get list request by filter - {}'.format(filters)) - response = self.api.get(url=self._list_url, params=filters) - return self.__loads_schema(response) diff --git a/connect/resource/fulfillment.py b/connect/resource/fulfillment.py index eeacbe2..2c12344 100644 --- a/connect/resource/fulfillment.py +++ b/connect/resource/fulfillment.py @@ -7,6 +7,8 @@ import json +from typing import List + from connect.logger import function_log from connect.models import FulfillmentSchema, Param, ActivationTileResponse from .base import BaseResource @@ -52,7 +54,7 @@ def render_template(self, pk, template_id): @function_log def update_parameters(self, pk, params): - # type: (str, list) -> str + # type: (str, List[Param]) -> str list_dict = [] for _ in params: list_dict.append(_.__dict__ if isinstance(_, Param) else _) diff --git a/connect/resource/fulfillment_automation.py b/connect/resource/fulfillment_automation.py index 95c5556..0e53dfa 100644 --- a/connect/resource/fulfillment_automation.py +++ b/connect/resource/fulfillment_automation.py @@ -4,7 +4,6 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ -from typing import Any from connect.logger import logger from connect.models import ActivationTemplateResponse, ActivationTileResponse @@ -16,19 +15,19 @@ class FulfillmentAutomation(FulfillmentResource): def process(self): - # type: () -> Any - for _ in self.list(): - return self.dispatch(_) + # type: () -> None + for _ in self.list: + self.dispatch(_) def dispatch(self, request): - # type: (Fulfillment) -> Any + # type: (Fulfillment) -> str try: logger.info('Start request process / ID request - {}'.format(request.id)) result = self.process_request(request) if not result: logger.info('Method `process_request` did not return result') - return + return '' params = {} if isinstance(result, ActivationTileResponse): @@ -48,8 +47,6 @@ def dispatch(self, request): except Skip as skip: return skip.code - return - def process_request(self, request): - # type: (Fulfillment) -> Any + # type: (Fulfillment) -> str raise NotImplementedError('Please implement `process_request` logic') diff --git a/connect/resource/template.py b/connect/resource/template.py index 45df821..78a065c 100644 --- a/connect/resource/template.py +++ b/connect/resource/template.py @@ -4,7 +4,7 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ -from typing import Any +from typing import List, Any from connect.models import ActivationTemplateResponse, ActivationTileResponse from .base import BaseResource @@ -18,6 +18,11 @@ class TemplateResource(BaseResource): """ resource = 'templates' + @property + def list(self): + # type: () -> List[Any] + raise AttributeError('This resource do not have method `list`') + def render(self, pk, request_id): # type: (str, str) -> ActivationTileResponse if not all([pk, request_id]): @@ -31,7 +36,3 @@ def render(self, pk, request_id): def get(self, pk): # type: (str) -> ActivationTemplateResponse return ActivationTemplateResponse(template_id=pk) - - def list(self): - # type: () -> Any - raise AttributeError('This resource do not have method `list`') diff --git a/example/example.py b/example/example.py index 3ec61fa..bf72107 100644 --- a/example/example.py +++ b/example/example.py @@ -4,6 +4,7 @@ This file is part of the Ingram Micro Cloud Blue Connect SDK. Copyright (c) 2019 Ingram Micro. All Rights Reserved. """ +from typing import Union from connect import FulfillmentAutomation from connect.config import Config @@ -21,7 +22,7 @@ class ExampleRequestProcessor(FulfillmentAutomation): def process_request(self, request): - # type: (Fulfillment) -> object + # type: (Fulfillment) -> Union[ActivationTemplateResponse, ActivationTileResponse] logger.info('Processing request {} for contract {}, product {}, marketplace {}' .format(request.id, diff --git a/tests/test_models.py b/tests/test_models.py index ace6f4f..4299a38 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -40,7 +40,7 @@ def test_create_model_from_response(): # Get requests from response resource = FulfillmentResource() - requests = resource.list() + requests = resource.list request_obj = resource.get(pk='PR-000-000-000') # Assert that all properties exist @@ -86,7 +86,7 @@ def test_create_model_from_response(): @patch('requests.get', MagicMock(return_value=_get_response2_ok())) def test_fulfillment_items(): # Get request - requests = FulfillmentResource().list() + requests = FulfillmentResource().list assert len(requests) == 1 request = requests[0] assert isinstance(request, Fulfillment) @@ -116,7 +116,7 @@ def test_fulfillment_items(): @patch('requests.get', MagicMock(return_value=_get_response2_ok())) def test_asset_methods(): # Get asset - requests = FulfillmentResource().list() + requests = FulfillmentResource().list assert len(requests) == 1 assert isinstance(requests[0], Fulfillment) asset = requests[0].asset