diff --git a/chargebee/model.py b/chargebee/model.py index b572da5..47ea2d4 100644 --- a/chargebee/model.py +++ b/chargebee/model.py @@ -4,16 +4,11 @@ class Model(object): fields = [] # field list repr_field = None # field to use for repr(), default is fields[0] + sub_types = {} # mapping {attr: type} + dependant_types = {} # mapping {attr: type}. If type is a 1-tuple, indicates it's a list. - def __init__(self, values, sub_types=None, dependant_types=None): - if sub_types is None: - sub_types = {} - if dependant_types is None: - dependant_types = {} - + def __init__(self, values): self.values = values - self.sub_types = sub_types - self.dependant_types = dependant_types for field in self.fields: setattr(self, field, None) @@ -49,21 +44,15 @@ def __getattr__(self, name): raise AttributeError("Attribute %s not found " % name) @classmethod - def construct(cls, values, sub_types=None, dependant_types=None): - obj = cls(values, sub_types, dependant_types) + def construct(cls, values): + obj = cls(values) obj.load(values) + for k, dependent_type in cls.dependant_types.items(): + if values.get(k) is not None: + if isinstance(dependent_type, tuple): + # dependent type being a 1-tuple indicates a list + set_val = [dependent_type[0].construct(v) for v in values[k]] + else: + set_val = dependent_type.construct(values[k]) + setattr(obj, k, set_val) return obj - - def init_dependant(self, obj, type, sub_types={}): - if obj.get(type) != None: - if isinstance(obj, dict) and type in self.dependant_types: - dependant_obj = self.dependant_types[type].construct(obj[type], sub_types) - setattr(self, type, dependant_obj) - - def init_dependant_list(self, obj, type, sub_types={}): - if obj.get(type) != None: - if isinstance(obj[type],(list, tuple)) and type in self.dependant_types: - if(self.dependant_types != None): - set_val = [self.dependant_types[type].construct(dt, sub_types) for dt in obj[type]] - setattr(self, type, set_val) - diff --git a/chargebee/models/credit_note.py b/chargebee/models/credit_note.py index 313c798..0c7801e 100644 --- a/chargebee/models/credit_note.py +++ b/chargebee/models/credit_note.py @@ -32,6 +32,16 @@ class Allocation(Model): "sub_total", "round_off_amount", "line_items", "discounts", "line_item_discounts", "taxes", \ "line_item_taxes", "linked_refunds", "allocations", "deleted"] + sub_types = { + 'line_items': LineItem, + 'discounts': Discount, + 'line_item_discounts': LineItemDiscount, + 'taxes': Tax, + 'line_item_taxes': LineItemTax, + 'linked_refunds': LinkedRefund, + 'allocations': Allocation, + } + @staticmethod def create(params, env=None, headers=None): diff --git a/chargebee/models/customer.py b/chargebee/models/customer.py index cc64001..12b2d97 100644 --- a/chargebee/models/customer.py +++ b/chargebee/models/customer.py @@ -30,6 +30,14 @@ class Balance(Model): "unbilled_charges", "refundable_credits", "excess_payments", "balances", "meta_data", "deleted", \ "registered_for_gst"] + sub_types = { + 'billing_address': BillingAddress, + 'referral_urls': ReferralUrl, + 'contacts': Contact, + 'payment_method': PaymentMethod, + 'balances': Balance, + } + @staticmethod def create(params=None, env=None, headers=None): diff --git a/chargebee/models/estimate.py b/chargebee/models/estimate.py index dd8320e..8eefe2f 100644 --- a/chargebee/models/estimate.py +++ b/chargebee/models/estimate.py @@ -3,11 +3,26 @@ from chargebee import request from chargebee import APIError +from chargebee.models.credit_note_estimate import CreditNoteEstimate +from chargebee.models.invoice_estimate import InvoiceEstimate +from chargebee.models.subscription_estimate import SubscriptionEstimate +from chargebee.models.unbilled_charge import UnbilledCharge + + class Estimate(Model): fields = ["created_at", "subscription_estimate", "invoice_estimate", "invoice_estimates", \ "next_invoice_estimate", "credit_note_estimates", "unbilled_charge_estimates"] + dependant_types = { + 'subscription_estimate': SubscriptionEstimate, + 'invoice_estimate': InvoiceEstimate, + 'next_invoice_estimate': InvoiceEstimate, + 'invoice_estimates': (InvoiceEstimate,), + 'credit_note_estimates': (CreditNoteEstimate,), + 'unbilled_charge_estimates': (UnbilledCharge,), + } + @staticmethod def create_subscription(params, env=None, headers=None): diff --git a/chargebee/models/event.py b/chargebee/models/event.py index f5196cb..7486d14 100644 --- a/chargebee/models/event.py +++ b/chargebee/models/event.py @@ -12,6 +12,11 @@ class Webhook(Model): fields = ["id", "occurred_at", "source", "user", "webhook_status", "webhook_failure_reason", \ "webhooks", "event_type", "api_version"] + sub_types = { + 'webhooks': Webhook, + } + + @property def content(self): from chargebee import Content @@ -23,12 +28,12 @@ def deserialize(json_data): webhook_data = json.loads(json_data) except (TypeError, ValueError) as ex: raise Exception("The passed json_data is not JSON formatted . " + ex.message) - + api_version = webhook_data.get('api_version', None) env_version = Environment.API_VERSION - if api_version != None and api_version.upper() != env_version.upper(): + if api_version != None and api_version.upper() != env_version.upper(): raise Exception("API version [" + api_version.upper() + "] in response does not match " - + "with client library API version [" + env_version.upper() + "]") + + "with client library API version [" + env_version.upper() + "]") return Event.construct(webhook_data) @staticmethod diff --git a/chargebee/models/invoice.py b/chargebee/models/invoice.py index 3315f33..077b419 100644 --- a/chargebee/models/invoice.py +++ b/chargebee/models/invoice.py @@ -54,6 +54,22 @@ class BillingAddress(Model): "taxes", "line_item_taxes", "linked_payments", "applied_credits", "adjustment_credit_notes", \ "issued_credit_notes", "linked_orders", "notes", "shipping_address", "billing_address", "deleted"] + sub_types = { + 'line_items': LineItem, + 'discounts': Discount, + 'line_item_discounts': LineItemDiscount, + 'taxes': Tax, + 'line_item_taxes': LineItemTax, + 'linked_payments': LinkedPayment, + 'applied_credits': AppliedCredit, + 'adjustment_credit_notes': AdjustmentCreditNote, + 'issued_credit_notes': IssuedCreditNote, + 'linked_orders': LinkedOrder, + 'notes': Note, + 'shipping_address': ShippingAddress, + 'billing_address': BillingAddress, + } + @staticmethod def create(params, env=None, headers=None): diff --git a/chargebee/models/invoice_estimate.py b/chargebee/models/invoice_estimate.py index b83ff8d..745b2d1 100644 --- a/chargebee/models/invoice_estimate.py +++ b/chargebee/models/invoice_estimate.py @@ -23,3 +23,10 @@ class LineItemDiscount(Model): fields = ["recurring", "price_type", "currency_code", "sub_total", "total", "credits_applied", \ "amount_paid", "amount_due", "line_items", "discounts", "taxes", "line_item_taxes", "line_item_discounts"] + sub_types = { + 'line_items': LineItem, + 'discounts': Discount, + 'taxes': Tax, + 'line_item_taxes': LineItemTax, + 'line_item_discounts': LineItemDiscount, + } diff --git a/chargebee/models/payment_source.py b/chargebee/models/payment_source.py index b4062b3..39d9ca2 100644 --- a/chargebee/models/payment_source.py +++ b/chargebee/models/payment_source.py @@ -21,6 +21,13 @@ class Paypal(Model): fields = ["id", "customer_id", "type", "reference_id", "status", "gateway", "gateway_account_id", \ "ip_address", "issuing_country", "card", "bank_account", "amazon_payment", "paypal"] + sub_types = { + 'card': Card, + 'bank_account': BankAccount, + 'amazon_payment': AmazonPayment, + 'paypal': Paypal, + } + @staticmethod def create_using_temp_token(params, env=None, headers=None): diff --git a/chargebee/models/portal_session.py b/chargebee/models/portal_session.py index 887b7ff..0b740c6 100644 --- a/chargebee/models/portal_session.py +++ b/chargebee/models/portal_session.py @@ -11,6 +11,10 @@ class LinkedCustomer(Model): fields = ["id", "token", "access_url", "redirect_url", "status", "created_at", "expires_at", \ "customer_id", "login_at", "logout_at", "login_ipaddress", "logout_ipaddress", "linked_customers"] + sub_types = { + 'linked_customers': LinkedCustomer + } + @staticmethod def create(params, env=None, headers=None): diff --git a/chargebee/models/subscription.py b/chargebee/models/subscription.py index 6b01e05..759bd68 100644 --- a/chargebee/models/subscription.py +++ b/chargebee/models/subscription.py @@ -27,6 +27,13 @@ class ReferralInfo(Model): "base_currency_code", "addons", "coupon", "coupons", "shipping_address", "referral_info", "invoice_notes", \ "meta_data", "deleted"] + sub_types = { + 'addons': Addon, + 'coupons': Coupon, + 'shipping_address': ShippingAddress, + 'referral_info': ReferralInfo, + } + @staticmethod def create(params, env=None, headers=None): diff --git a/chargebee/models/subscription_estimate.py b/chargebee/models/subscription_estimate.py index 7c1d4b2..15df8b0 100644 --- a/chargebee/models/subscription_estimate.py +++ b/chargebee/models/subscription_estimate.py @@ -11,3 +11,6 @@ class ShippingAddress(Model): fields = ["id", "currency_code", "status", "next_billing_at", "shipping_address"] + sub_types = { + 'shipping_address': ShippingAddress, + } diff --git a/chargebee/models/transaction.py b/chargebee/models/transaction.py index e64c8b4..4f854ea 100644 --- a/chargebee/models/transaction.py +++ b/chargebee/models/transaction.py @@ -21,6 +21,12 @@ class LinkedRefund(Model): "refunded_txn_id", "reversal_transaction_id", "linked_invoices", "linked_credit_notes", "linked_refunds", \ "deleted"] + sub_types = { + 'linked_invoices': LinkedInvoice, + 'linked_credit_notes': LinkedCreditNote, + 'linked_refunds': LinkedRefund, + } + @staticmethod def list(params=None, env=None, headers=None): diff --git a/chargebee/result.py b/chargebee/result.py index 2c066cc..6fe4820 100644 --- a/chargebee/result.py +++ b/chargebee/result.py @@ -10,196 +10,169 @@ def __init__(self, response): @property def subscription(self): - subscription = self._get('subscription', Subscription, - {'addons' : Subscription.Addon, 'coupons' : Subscription.Coupon, 'shipping_address' : Subscription.ShippingAddress, 'referral_info' : Subscription.ReferralInfo}); + subscription = self._get('subscription', Subscription) return subscription; @property def customer(self): - customer = self._get('customer', Customer, - {'billing_address' : Customer.BillingAddress, 'referral_urls' : Customer.ReferralUrl, 'contacts' : Customer.Contact, 'payment_method' : Customer.PaymentMethod, 'balances' : Customer.Balance}); + customer = self._get('customer', Customer) return customer; @property def payment_source(self): - payment_source = self._get('payment_source', PaymentSource, - {'card' : PaymentSource.Card, 'bank_account' : PaymentSource.BankAccount, 'amazon_payment' : PaymentSource.AmazonPayment, 'paypal' : PaymentSource.Paypal}); + payment_source = self._get('payment_source', PaymentSource) return payment_source; @property def third_party_payment_method(self): - third_party_payment_method = self._get('third_party_payment_method', ThirdPartyPaymentMethod); + third_party_payment_method = self._get('third_party_payment_method', ThirdPartyPaymentMethod) return third_party_payment_method; @property def card(self): - card = self._get('card', Card); + card = self._get('card', Card) return card; @property def promotional_credit(self): - promotional_credit = self._get('promotional_credit', PromotionalCredit); + promotional_credit = self._get('promotional_credit', PromotionalCredit) return promotional_credit; @property def invoice(self): - invoice = self._get('invoice', Invoice, - {'line_items' : Invoice.LineItem, 'discounts' : Invoice.Discount, 'line_item_discounts' : Invoice.LineItemDiscount, 'taxes' : Invoice.Tax, 'line_item_taxes' : Invoice.LineItemTax, 'linked_payments' : Invoice.LinkedPayment, 'applied_credits' : Invoice.AppliedCredit, 'adjustment_credit_notes' : Invoice.AdjustmentCreditNote, 'issued_credit_notes' : Invoice.IssuedCreditNote, 'linked_orders' : Invoice.LinkedOrder, 'notes' : Invoice.Note, 'shipping_address' : Invoice.ShippingAddress, 'billing_address' : Invoice.BillingAddress}); + invoice = self._get('invoice', Invoice) return invoice; @property def credit_note(self): - credit_note = self._get('credit_note', CreditNote, - {'line_items' : CreditNote.LineItem, 'discounts' : CreditNote.Discount, 'line_item_discounts' : CreditNote.LineItemDiscount, 'taxes' : CreditNote.Tax, 'line_item_taxes' : CreditNote.LineItemTax, 'linked_refunds' : CreditNote.LinkedRefund, 'allocations' : CreditNote.Allocation}); + credit_note = self._get('credit_note', CreditNote) return credit_note; @property def unbilled_charge(self): - unbilled_charge = self._get('unbilled_charge', UnbilledCharge); + unbilled_charge = self._get('unbilled_charge', UnbilledCharge) return unbilled_charge; @property def order(self): - order = self._get('order', Order); + order = self._get('order', Order) return order; @property def transaction(self): - transaction = self._get('transaction', Transaction, - {'linked_invoices' : Transaction.LinkedInvoice, 'linked_credit_notes' : Transaction.LinkedCreditNote, 'linked_refunds' : Transaction.LinkedRefund}); + transaction = self._get('transaction', Transaction) return transaction; @property def hosted_page(self): - hosted_page = self._get('hosted_page', HostedPage); + hosted_page = self._get('hosted_page', HostedPage) return hosted_page; @property def estimate(self): - estimate = self._get('estimate', Estimate, {}, - {'subscription_estimate' : SubscriptionEstimate, 'invoice_estimate' : InvoiceEstimate, 'invoice_estimates' : InvoiceEstimate, 'next_invoice_estimate' : InvoiceEstimate, 'credit_note_estimates' : CreditNoteEstimate, 'unbilled_charge_estimates' : UnbilledCharge}); - estimate.init_dependant(self._response['estimate'], 'subscription_estimate', - {'shipping_address' : SubscriptionEstimate.ShippingAddress}); - estimate.init_dependant(self._response['estimate'], 'invoice_estimate', - {'line_items' : InvoiceEstimate.LineItem, 'discounts' : InvoiceEstimate.Discount, 'taxes' : InvoiceEstimate.Tax, 'line_item_taxes' : InvoiceEstimate.LineItemTax, 'line_item_discounts' : InvoiceEstimate.LineItemDiscount}); - estimate.init_dependant(self._response['estimate'], 'next_invoice_estimate', - {'line_items' : InvoiceEstimate.LineItem, 'discounts' : InvoiceEstimate.Discount, 'taxes' : InvoiceEstimate.Tax, 'line_item_taxes' : InvoiceEstimate.LineItemTax, 'line_item_discounts' : InvoiceEstimate.LineItemDiscount}); - estimate.init_dependant_list(self._response['estimate'], 'invoice_estimates', - {'line_items' : InvoiceEstimate.LineItem, 'discounts' : InvoiceEstimate.Discount, 'taxes' : InvoiceEstimate.Tax, 'line_item_taxes' : InvoiceEstimate.LineItemTax, 'line_item_discounts' : InvoiceEstimate.LineItemDiscount}); - estimate.init_dependant_list(self._response['estimate'], 'credit_note_estimates', - {'line_items' : CreditNoteEstimate.LineItem, 'discounts' : CreditNoteEstimate.Discount, 'taxes' : CreditNoteEstimate.Tax, 'line_item_taxes' : CreditNoteEstimate.LineItemTax, 'line_item_discounts' : CreditNoteEstimate.LineItemDiscount}); - estimate.init_dependant_list(self._response['estimate'], 'unbilled_charge_estimates', - {}); + estimate = self._get('estimate', Estimate) return estimate; @property def plan(self): - plan = self._get('plan', Plan); + plan = self._get('plan', Plan) return plan; @property def addon(self): - addon = self._get('addon', Addon); + addon = self._get('addon', Addon) return addon; @property def coupon(self): - coupon = self._get('coupon', Coupon); + coupon = self._get('coupon', Coupon) return coupon; @property def coupon_set(self): - coupon_set = self._get('coupon_set', CouponSet); + coupon_set = self._get('coupon_set', CouponSet) return coupon_set; @property def coupon_code(self): - coupon_code = self._get('coupon_code', CouponCode); + coupon_code = self._get('coupon_code', CouponCode) return coupon_code; @property def address(self): - address = self._get('address', Address); + address = self._get('address', Address) return address; @property def event(self): - event = self._get('event', Event, - {'webhooks' : Event.Webhook}); + event = self._get('event', Event) return event; @property def comment(self): - comment = self._get('comment', Comment); + comment = self._get('comment', Comment) return comment; @property def download(self): - download = self._get('download', Download); + download = self._get('download', Download) return download; @property def portal_session(self): - portal_session = self._get('portal_session', PortalSession, - {'linked_customers' : PortalSession.LinkedCustomer}); + portal_session = self._get('portal_session', PortalSession) return portal_session; @property def site_migration_detail(self): - site_migration_detail = self._get('site_migration_detail', SiteMigrationDetail); + site_migration_detail = self._get('site_migration_detail', SiteMigrationDetail) return site_migration_detail; @property def resource_migration(self): - resource_migration = self._get('resource_migration', ResourceMigration); + resource_migration = self._get('resource_migration', ResourceMigration) return resource_migration; @property def time_machine(self): - time_machine = self._get('time_machine', TimeMachine); + time_machine = self._get('time_machine', TimeMachine) return time_machine; @property def unbilled_charges(self): - unbilled_charges = self._get_list('unbilled_charges', UnbilledCharge, - {}); + unbilled_charges = self._get_list('unbilled_charges', UnbilledCharge) return unbilled_charges; @property def credit_notes(self): - credit_notes = self._get_list('credit_notes', CreditNote, - {'line_items' : CreditNote.LineItem, 'discounts' : CreditNote.Discount, 'line_item_discounts' : CreditNote.LineItemDiscount, 'taxes' : CreditNote.Tax, 'line_item_taxes' : CreditNote.LineItemTax, 'linked_refunds' : CreditNote.LinkedRefund, 'allocations' : CreditNote.Allocation}); + credit_notes = self._get_list('credit_notes', CreditNote) return credit_notes; @property def invoices(self): - invoices = self._get_list('invoices', Invoice, - {'line_items' : Invoice.LineItem, 'discounts' : Invoice.Discount, 'line_item_discounts' : Invoice.LineItemDiscount, 'taxes' : Invoice.Tax, 'line_item_taxes' : Invoice.LineItemTax, 'linked_payments' : Invoice.LinkedPayment, 'applied_credits' : Invoice.AppliedCredit, 'adjustment_credit_notes' : Invoice.AdjustmentCreditNote, 'issued_credit_notes' : Invoice.IssuedCreditNote, 'linked_orders' : Invoice.LinkedOrder, 'notes' : Invoice.Note, 'shipping_address' : Invoice.ShippingAddress, 'billing_address' : Invoice.BillingAddress}); + invoices = self._get_list('invoices', Invoice) return invoices; - - def _get_list(self, type, cls, sub_types={}, dependant_types={}, dependant_sub_types={}): + def _get_list(self, type, cls): if not type in self._response: return None set_val = [] for obj in self._response[type]: if isinstance(obj, dict): - model = cls.construct(obj, sub_types, dependant_types) - for k in dependant_sub_types: - model.init_dependant(obj, k, dependant_sub_types[k]) + model = cls.construct(obj) set_val.append(model) self._response_obj[type] = set_val return self._response_obj[type] - def _get(self, type, cls, sub_types=None, dependant_types=None): + def _get(self, type, cls): if not type in self._response: return None if not type in self._response_obj: - self._response_obj[type] = cls.construct(self._response[type], sub_types, dependant_types) + self._response_obj[type] = cls.construct(self._response[type]) return self._response_obj[type] @@ -209,5 +182,6 @@ def __str__(self): def __repr__(self): return "".format(";".join(self._response.keys())) + class Content(Result): pass diff --git a/tests/result.py b/tests/result.py new file mode 100644 index 0000000..8fbd4f2 --- /dev/null +++ b/tests/result.py @@ -0,0 +1,120 @@ +import unittest + +from chargebee import Customer, Estimate +from chargebee.models.invoice_estimate import InvoiceEstimate + + +class ResultTest(unittest.TestCase): + def test_construct(self): + values = { + "id": "XpbGElGQgEIrbF77", + "first_name": "John", + "last_name": "Doe", + "email": "john@test.com", + "auto_collection": "on", + "net_term_days": 0, + "allow_direct_debit": False, + "created_at": 1515495068, + "taxability": "taxable", + "updated_at": 1515495068, + "locale": "fr-CA", + "resource_version": 1515495068000, + "deleted": False, + "object": "customer", + "billing_address": { + "first_name": "John", + "last_name": "Doe", + "line1": "PO Box 9999", + "city": "Walnut", + "state_code": "CA", + "state": "California", + "country": "US", + "zip": "91789", + "validation_status": "not_validated", + "object": "billing_address" + }, + "contacts": [ + { + "id": "ty68op521m", + "first_name": "Michel", + "last_name": "Ross", + "email": "Mike@test.com", + "label": "Mike", + "enabled": True, + "send_account_email": True, + "send_billing_email": False, + "object": "contact" + }, + ], + "card_status": "no_card", + "promotional_credits": 0, + "refundable_credits": 0, + "excess_payments": 0, + "unbilled_charges": 0, + "preferred_currency_code": "USD" + } + + cust = Customer.construct(values) + + self.assertIsInstance(cust, Customer) + self.assertIsInstance(cust.billing_address, Customer.BillingAddress) + self.assertEqual(cust.billing_address.zip, "91789") + + self.assertIsNone(cust.payment_method) + + self.assertIsInstance(cust.contacts, list) + self.assertIsInstance(cust.contacts[0], Customer.Contact) + self.assertEqual(cust.contacts[0].id, "ty68op521m") + + def test_dependent_sub_types(self): + values = { + "created_at": 1515494922, + "object": "estimate", + "invoice_estimates": [ + { + "recurring": True, + "price_type": "tax_exclusive", + "sub_total": 2000, + "total": 2000, + "credits_applied": 0, + "amount_paid": 0, + "amount_due": 2000, + "object": "invoice_estimate", + "line_items": [ + { + "id": "li_XpbGElGQgEIDTu2k", + "date_from": 1515494914, + "date_to": 1518173314, + "unit_amount": 1000, + "quantity": 1, + "is_taxed": False, + "tax_amount": 0, + "object": "line_item", + "subscription_id": "addams", + "amount": 1000, + "description": "No Trial", + "entity_type": "plan", + "entity_id": "no_trial", + "discount_amount": 0, + "item_level_discount_amount": 0 + }, + ], + "taxes": [], + "line_item_taxes": [], + "currency_code": "USD", + "line_item_discounts": [] + }, + ] + } + estimate = Estimate.construct(values) + + self.assertIsInstance(estimate, Estimate) + self.assertIsInstance(estimate.invoice_estimates, list) + + inv_estimate = estimate.invoice_estimates[0] + self.assertIsInstance(inv_estimate, InvoiceEstimate) + self.assertEqual(inv_estimate.currency_code, "USD") + + self.assertIsInstance(inv_estimate.line_items, list) + self.assertIsInstance(inv_estimate.line_items[0], InvoiceEstimate.LineItem) + self.assertEqual(inv_estimate.line_items[0].id, "li_XpbGElGQgEIDTu2k")