Git Product home page Git Product logo

chartmogul-python's Introduction

Official ChartMogul API Python Client

chartmogul-python provides convenient Python bindings for ChartMogul's API.

PyPI version Build Status


Installation | Configuration | Usage | Development | Contributing | License



Installation

This library requires Python 3.8 to 3.12. It was last tested against Python 2.7 in version 1.3.0.

pip3 install chartmogul

Configuration

First create a Config object by passing your API key, available from the administration section of your ChartMogul account. You need to pass this configuration object as the first argument to each request.

import chartmogul
config = chartmogul.Config('api_key')

Alternatively, you can use the library without the module prefix:

from chartmogul import *
config = Config('api_key')

Note that both ways should import all necessary classes and submodules, but the first one is preferred due to being explicit.

To test authentication, try ping endpoint:

import chartmogul
chartmogul.Ping.ping(config).get()

This throws error or returns <Ping{data='pong!'}>

Options

You can also pass to the Config initializer:

  • request_timeout= sets timeout for requests (seconds), default: none (see requests docs for details)

Rate Limits & Exponential Backoff

The library will keep retrying if the request exceeds the rate limit or if there's any network related error. By default, the request will be retried for 20 times (approximately 15 minutes) before finally giving up.

You can change the retry count from the Config initializer:

  • max_retries= sets the maximum number of retries for failed requests, default: 20
  • backoff_factor= sets the exponential backoff factor, default: 2

Set max_retries 0 to disable it. Set backoff_factor 0 to disable it.

Usage

The library is based on promises (mechanism similar to futures). Every call therefore returns an object like <promise.promise.Promise object at 0x123456789> The requests are running asynchronously, but you can use them synchronously, just appending .get(), which will block your program until the response has come. Both sync/async ways will return/pass native Python objects mapping to the entities in the API, or raise/pass errors from the API, see an example:

import chartmogul

config = chartmogul.Config('api_key')
req = chartmogul.Plan.create(config, data={'name': 'Awesome plan'...})

# Now either asynchronous reaction:
req.then(lambda plan: print(plan)).catch(reactOnException)

# or synchronous:
try:
    print(req.get())
except Exception as ex:
    reactOnException(ex)

Import API

Available methods in Import API:

chartmogul.DataSource.create(config, data={'name': 'In-house billing'})
chartmogul.DataSource.retrieve(config, uuid='ds_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.DataSource.all(config)
chartmogul.DataSource.destroy(config, uuid='ds_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Customer.create(config, data={})
chartmogul.Customer.all(config, cursor='cursor==', per_page=20)
chartmogul.Customer.retrieve(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Customer.search(config, email='[email protected]')
chartmogul.Customer.merge(config, data={
  'from': {'customer_uuid': 'cus_5915ee5a-babd-406b-b8ce-d207133fb4cb'},
  'into': {'customer_uuid': 'cus_2123290f-09c8-4628-a205-db5596bd58f7'}
})
chartmogul.Customer.modify(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  "city": "San Francisco",
  "country": "US",
  "state": "CA",
})
chartmogul.Customer.destroy(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Customer.connectSubscriptions(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'subscriptions': [
    {
      "data_source_uuid": "ds_ade45e52-47a4-231a-1ed2-eb6b9e541213",
      "external_id": "d1c0c885-add0-48db-8fa9-0bdf5017d6b0"
    },
    {
      "data_source_uuid": "ds_ade45e52-47a4-231a-1ed2-eb6b9e541213",
      "external_id": "9db5f4a1-1695-44c0-8bd4-de7ce4d0f1d4"
    }
  ]
})
chartmogul.Customer.contacts(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
chartmogul.Customer.createContact(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
chartmogul.Customer.notes(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
chartmogul.Customer.createNote(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
chartmogul.Customer.opporunities(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
chartmogul.Customer.createOpportunity(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
chartmogul.Contact.create(config, data={})
chartmogul.Contact.all(config, cursor='aabbcc', per_page=20)
chartmogul.Contact.retrieve(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Contact.merge(config, into_uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', from_uuid='con_2123290f-09c8-4628-a205-db5596bd58f7')
chartmogul.Contact.modify(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  "email": "[email protected]"
})
chartmogul.Contact.destroy(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.CustomerNote.create(config, data={})
chartmogul.CustomerNote.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.CustomerNote.retrieve(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.CustomerNote.patch(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.CustomerNote.destroy(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Opportunity.create(config, data={})
chartmogul.Opportunity.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Opportunity.retrieve(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Opportunity.patch(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Opportunity.destroy(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')

Note that the returned attributes of type date are not parsed and stay in string.

chartmogul.Attributes.retrieve(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Tags.add(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'tags': ['important', 'Prio1']
})
chartmogul.Tags.add(config, data={
  'email': '[email protected]',
  'tags': ['important', 'Prio1']
})
chartmogul.Tags.remove(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'tags': ['important', 'Prio1']
})
chartmogul.CustomAttributes.add(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'custom': [
    {'type': 'Integer', 'key': 'age', 'value': 8}
  ]
})
chartmogul.CustomAttributes.add(config, data={
  'email': '[email protected]',
  'custom': [
    {'type': 'Integer', 'key': 'age', 'value': 8}
  ]
})
chartmogul.CustomAttributes.update(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'custom': {
    'age': 20,
    'channel': 'Twitter'
  }
});
chartmogul.CustomAttributes.remove(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'custom': ['CAC']
})
chartmogul.Plan.create(config, data={})
chartmogul.Plan.retrieve(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Plan.modify(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
    'name': 'new name'
})
chartmogul.Plan.all(config, cursor='cursor==', external_id='')
chartmogul.Plan.destroy(config, uuid='')
chartmogul.PlanGroup.create(config, data={})
chartmogul.PlanGroup.retrieve(config, uuid='plg_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.PlanGroup.modify(config, uuid='plg_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
chartmogul.PlanGroup.all(config, cursor='cursor==')
chartmogul.PlanGroup.all(config, uuid='plg_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.PlanGroup.destroy(config, uuid='')
import chartmogul

chartmogul.Invoice.create(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
chartmogul.Invoice.all(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='cursor==', per_page=10)
chartmogul.Invoice.all(config, customer_uuid='cus_f466e33d-ff2b-4a11-8f85-417eb02157a7', external_id='INV0001')
chartmogul.Invoice.retrieve(config, uuid='inv_22910fc6-c931-48e7-ac12-90d2cb5f0059')
import chartmogul

chartmogul.Transaction.create(config, uuid='inv_745df1d4-819f-48ee-873d-b5204801e021', data={})
import chartmogul
chartmogul.SubscriptionEvent.all(config)
chartmogul.SubscriptionEvent.create(config, data={
  'subscription_event' : {
    'external_id' : 'evnt_026',
    'customer_external_id' : 'scus_022',
    'data_source_uuid' : 'ds_1fm3eaac-62d0-31ec-clf4-4bf0mbe81aba',
    'event_type' : 'subscription_start_scheduled',
    'event_date' : '2022-03-30',
    'effective_date' : '2022-04-01',
    'subscription_external_id' : 'sub_0001',
    'plan_external_id' : 'gol d_monthly',
    'currency' : 'USD',
    'amount_in_cents' : 1000,
    'quantity': 1
}})
chartmogul.SubscriptionEvent.modify_with_params(config, data={
'subscription_event' : {
    'id' : 73966836,
    'currency' : 'EUR',
    'amount_in_cents' : 100
}})
chartmogul.SubscriptionEvent.destroy_with_params(config, data={
'subscription_event' : {
    'id' : 73966836
}})
import chartmogul

chartmogul.Subscription.list_imported(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Subscription.cancel(config, uuid='sub_3995ee5a-bbdb-406b-a8ca-d207133fb9bb' data={'cancelled_at': ''})
chartmogul.Subscription.modify(config, uuid='sub_3995ee5a-bbdb-406b-a8ca-d207133fb9bb' data={'cancellation_dates': []})

Available methods in Metrics API:

chartmogul.Metrics.all(config,
                       start_date='2015-01-01', # notice the _ here
                       end_date='2015-11-24',
                       interval='month',
                       geo='GB',
                       plans='Bronze Plan'
)
chartmogul.Metrics.mrr(config,
                       start_date='2015-01-01',
                       end_date='2015-11-24',
                       interval='month',
                       geo='GB',
                       plans='PRO Plan')
chartmogul.Metrics.arr(config, data={})
chartmogul.Metrics.arpa(config, data={})
chartmogul.Metrics.asp(config, data={})
chartmogul.Metrics.customer_count(config, data={})
chartmogul.Metrics.customer_churn_rate(config, data={})
chartmogul.Metrics.mrr_churn_rate(config, data={})
chartmogul.Metrics.ltv(config, data={})

chartmogul.CustomerActivity.all(config, uuid='')
chartmogul.CustomerSubscription.all(config, uuid='cus_cf121bf8-db58-11ec-8575-43bda9fb67ad')
chartmogul.Activity.all(config, start_date='2020-06-02T00:00:00Z')

chartmogul.ActivitiesExport.create(config,data={})
chartmogul.ActivitiesExport.create(config, data={'type':'churn', 'start-date':'2020-06-02T00:00:00Z'})
chartmogul.ActivitiesExport.retrieve(config, id='febbd467-40d4-4f99-a647-3f47333453db')

Account

Available methods:

chartmogul.Account.retrieve(config)

Errors

The library throws TypeError if data parameter is not serializable.

The chartmogul.ArgumentMissingError is raised if obligatory uuid or data is missing in the call.

The error chartmogul.APIError is raised for any non-20x response from the API. It always has cause of type requests.HTTPError, which contains the response, so you can extract JSON/text in the following way (and program reaction):

from pprint import pprint
try:
    chartmogul.doStuff()
except chartmogul.APIError as e:
    response = e.__cause__.response
    try:
        pprint(response.json())
    except ValueError:
        pprint(response.text)

The cause default serialization doesn't give the API user much detail:

HTTPError('422 Client Error: Unprocessable Entity for url: https://api.chartmogul.com/v1/data_sources',)

That's why it's wrapped, so that you get the detail by default.

APIError(b'{"errors":{"name":"Has already been taken."}}',)

Development

To work on the library:

  • Fork it
  • Create your feature branch (git checkout -b my-new-feature)
  • Setup a virtual environment (python -m venv <directory>)
  • Activate the virtual environment (source <directory>/bin/activate)
  • Install dependencies: pip3 install -r requirements.txt && python3 setup.py develop
  • Fix bugs or add features. Make sure the changes pass the coding guidelines (use pylama).
  • Write tests for your new features. Use requests_mock for HTTP mocking.
    • To run test install requirement-test pip3 install -r requirements-test.txt and run with python -m unittest:
      • pip3 install coverage
      • coverage run ./setup.py test
      • coverage html --include='chartmogul/*'
      • Find results in htmlcov/index.html
  • If all tests are passed, push to the branch (git push origin my-new-feature)
  • Create a new Pull Request

For a testing project we recommend setting up a virtualenv with development mode installation and Jupyter. virtualenvwrapper is another handy tool.

Built using Requests.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/chartmogul/chartmogul-python.

Releasing

Make sure that:

  1. you have prepared ~/.pypirc with credentials,
  2. a higher version has been set in chartmogul/__init__.py,
  3. Test & build package python3 setup.py test sdist
  4. release works twine upload --repository-url https://test.pypi.org/legacy/ dist/*,
  5. release to production twine upload dist/*,

Read full HOWTO

License

The library is available as open source under the terms of the MIT License.

The MIT License (MIT)

Copyright (c) 2016 ChartMogul Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

chartmogul-python's People

Contributors

alesdotio avatar alpdal avatar bilbof avatar briwa avatar claudiadogaru avatar davidlacho avatar dina920 avatar hassansin avatar jdevera avatar jeffreykuiken avatar kamilpavlicko avatar kawa-marcin avatar kmossco avatar mariabraganca avatar mynameispedro avatar pabragin avatar pegler avatar pkivana avatar pkopac avatar polis80cy avatar sbrych avatar soeunsona avatar spyrbri avatar srikalab avatar swember avatar velinarusjakova avatar viktorchukhantsev avatar whobubble avatar ytvinay avatar zlekki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chartmogul-python's Issues

Promise to Dictionary

Hi, I would like to have plans in dictionary format but chartmogul.Plan.all(config, per_page=3) returns a promise(?).
What is the easiest way to retrieve data from this object?

Thank you very much!

How do I actually use promises?

Can you guys please provide a practical example of using promises to use the API asynchronously? It's just super unclear, and nothing I'm trying is working.

405 Method not allowed on Invoice.create

When I try to create an Invoice I get this exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/promise/promise.py", line 65, in try_catch
    return (handler(*args, **kwargs), None)
  File "/usr/local/lib/python3.6/site-packages/chartmogul/resource.py", line 85, in _load
    response.raise_for_status()
  File "/usr/local/lib/python3.6/site-packages/requests/models.py", line 935, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: https://api.chartmogul.com/v1/import/customers/invoices

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<console>", line 2, in <module>
  File "/Users/sento/dev/test/chartmogul.py", line 66, in create_invoice
    return chartmogul.Invoice.create(config, uuid=cus_uuid, data=data).get().uuid
  File "/usr/local/lib/python3.6/site-packages/promise/promise.py", line 423, in get
    return self._target_settled_value(_raise=True)
  File "/usr/local/lib/python3.6/site-packages/promise/promise.py", line 426, in _target_settled_value
    return self._target()._settled_value(_raise)
  File "/usr/local/lib/python3.6/site-packages/promise/promise.py", line 187, in _settled_value
    reraise(type(raise_val), raise_val, self._traceback)
  File "/usr/local/lib/python3.6/site-packages/six.py", line 693, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/promise/promise.py", line 65, in try_catch
    return (handler(*args, **kwargs), None)
  File "/usr/local/lib/python3.6/site-packages/chartmogul/errors.py", line 19, in annotateHTTPError
    raise_from(APIError(err.response.content), err)
  File "/usr/local/lib/python3.6/site-packages/future/utils/__init__.py", line 398, in raise_from
    exec(execstr, myglobals, mylocals)
  File "<string>", line 1, in <module>
chartmogul.errors.APIError: b'""'

effective_date and event_date data types in subscription_event.py

I'm trying to use the chartmogul.SubscriptionEvent.create() method using the provided code sample and keep getting the following error: marshmallow.exceptions.ValidationError: {'effective_date': ['Not a valid date.'], 'event_date': ['Not a valid date.']}

I've tried different date formats including using the date package to no avail.

I found that if I change the data type definitions in subscription_event.py from fields.Date() to fields.DateTime() everything works as expected. Also, I notice that almost everywhere a date is used it is a DateTime data type except for here and once instance in metrics.py.

I don't see that this has been reported by anyone else, so possibly there's some other issue. I also don't know if simply changing the data types is an appropriate action. But please let me know how to proceed.

making sense of chartmogul.Customer.modify

I think I've managed to suss out how this works, but the documentation and code needs some updating.

The README.md has this example that seems like a bad copy-paste from merge:

chartmogul.Customer.modify(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  'from': {'customer_uuid': 'cus_5915ee5a-babd-406b-b8ce-d207133fb4cb'},
  'into': {'customer_uuid': 'cus_2123290f-09c8-4628-a205-db5596bd58f7'}
})

Maybe should be replaced with something like (taken from the API docs):

chartmogul.Customer.modify(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
  "city": "San Francisco",
  "country": "US",
  "state": "CA",
})

Also, both the Update a Customer docs and code seem to imply you can call modify without any UUID. I haven't tried it, but I suspect it doesn't work. If it should be required, it seems like modify (and any of the other patch actions) should be added here:

if method in [
'destroy',
'cancel',
'retrieve',
'update'] and 'uuid' not in kwargs:
If it does work without a UUID, it'd be nice if the docs explained how it resolves which document it updates and which pieces of information in data are required for that to work.

Incidentally, it seems like the library allows a chartmogul.Customer.update call which does a PUT. I'm not sure if this actually works, but if the API allows it then it seems like a "foot gun" where people will by trying to "Update a Customer" but actually replacing the whole document with just the parts they want to update.

Invoice api returns undocumented field (has_more) and breaks _many named tuple mapping.

Hi @swember and @pkopac, I'm opening a new issue as #80 is now closed.

I'm seeing this issue too. Are you sure this fix was deployed? Or maybe this bug has made its way back into production again?

Code:
invoices = chartmogul.Invoice.all(config,external_id=order_id).get();

Error:
TypeError: () got an unexpected keyword argument 'has_more'

I've checked the API and it is returning a field called has_more on the JSON payload, but it seems that the invoice schema doesn't handle this.

If anyone is experiencing this issue, changing invoice.py
_many = namedtuple('Invoices', [_root_key, "current_page", "total_pages", "customer_uuid"])
to
_many = namedtuple('Invoices', [_root_key, "current_page", "total_pages", "has_more", "customer_uuid"])
fixes the issue.

Due date field may not be null

The due date field is optional, so we should allow this to be null. This should do it:

        due_date = fields.DateTime(allow_none=True)

Import Error Roadblock

Very basic initial tests with this resulted in issues. Anyone find a resolution?

Simple ping script per documentation:
`import chartmogul

config = chartmogul.Config('TOKEN','SECRET')
chartmogul.Ping.ping(config).get()'

Result:
'Traceback (most recent call last):
File "./ping.py", line 2, in
import chartmogul
File "/Library/Python/2.7/site-packages/chartmogul/init.py", line 2, in
from .api.config import Config
ImportError: No module named api.config`

Upgrade the Marshmallow version

Hi, are you considering upgrade of the Marshmallow version to the v3.x? We're using chartmogul in our project and it's our only blocker for upgrading the Marshmallow to v3.

Thanks!

Invoices plan_uuid should be optional

Due to empty plan_uuid fields, our ETL is breaking with promise not valid error marshmallow.exceptions.ValidationError: {118: {'line_items': {0: {'plan_uuid': ['Field may not be null.']}}}} not allowing us to get any invoices even though only one invoice has empty plan_uuid.

I'm wondering if plan_uuid could be considered optional (and what the overall impact would be) by changing, plan_uuid = fields.String(allow_none=True) inside invoices.py

docs for "Delete all Customer's Invoices" is wrong

Found an error in the API docs on https://dev.chartmogul.com/reference/delete-all-customers-invoices

The example for Python just gives an error.

>>> chartmogul.Invoice.destroy_all(
...     config, 
...     'ds_fef05d54-47b4-431b-aed2-eb6b9e545430', 
...     'cus_f466e33d-ff2b-4a11-8f85-417eb02157a'
... ).get()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: fc() takes 2 positional arguments but 4 were given

It looks like the required syntax is this (named keywords instead of positional):

chartmogul.Invoice.destroy_all(
    config,
    data_source_uuid='ds_fef05d54-47b4-431b-aed2-eb6b9e545430',
    customer_uuid='cus_f466e33d-ff2b-4a11-8f85-417eb02157a'
).get()

Unknown fields error during response parsing

Hi, I'm using library version 2.1.0 (current release), and am having trouble with response deserialization in Marshmallow.

I'm sending an invoice create that looks like this:

chartmogul.Invoice.create(self.config, uuid=key, data={"invoices": invoices}).get()

The value of invoices is an array that includes all of the proper fields.

During the response cycle, the library deserializes the API's JSON response into Marshmallow models, using the Schema meta class. During this process, if there are any fields from the API that are NOT found in the Marshmallow models, you will get a marshmallow.exceptions.ValidationError.

I'm guessing you have added a few new fields to the API response, but the library is not aware of them. In my case, I'm seeing the following stack trace:

<class 'marshmallow.exceptions.ValidationError'>
{0: {'line_items': {0: {'transaction_fees_currency': ['Unknown field.'], 'discount_description': ['Unknown field.']}}}}
  File <redacted>
    chartmogul.Invoice.create(self.config, uuid=key, data={"invoices": invoices}).get()
  File "/usr/local/lib/python3.8/dist-packages/promise/promise.py", line 512, in get
    return self._target_settled_value(_raise=True)
  File "/usr/local/lib/python3.8/dist-packages/promise/promise.py", line 516, in _target_settled_value
    return self._target()._settled_value(_raise)
  File "/usr/local/lib/python3.8/dist-packages/promise/promise.py", line 226, in _settled_value
    reraise(type(raise_val), raise_val, self._traceback)
  File "/usr/local/lib/python3.8/dist-packages/six.py", line 719, in reraise
    raise value
  File "/usr/local/lib/python3.8/dist-packages/promise/promise.py", line 87, in try_catch
    return (handler(*args, **kwargs), None)
  File "/usr/local/lib/python3.8/dist-packages/chartmogul/errors.py", line 21, in annotateHTTPError
    raise err
  File "/usr/local/lib/python3.8/dist-packages/promise/promise.py", line 87, in try_catch
    return (handler(*args, **kwargs), None)
  File "/usr/local/lib/python3.8/dist-packages/chartmogul/resource.py", line 93, in _load
    return cls._loadJSON(jsonObj)
  File "/usr/local/lib/python3.8/dist-packages/chartmogul/resource.py", line 99, in _loadJSON
    return cls._many(cls._schema.load(jsonObj[cls._root_key], many=True), **{
  File "/usr/local/lib/python3.8/dist-packages/marshmallow/schema.py", line 719, in load
    return self._do_load(
  File "/usr/local/lib/python3.8/dist-packages/marshmallow/schema.py", line 901, in _do_load
    raise exc

These 2 fields, transaction_fees_currency and discount_description, are not in the documentation or the library anywhere, but do appear to be in the JSON response from your API, as mentioned.

Perhaps the Invoice schema meta could be updated to use Marshmallow's IGNORE?

chartmogul.Customer.all fails due to incorrect field setup in Customer model

customer.py line 69 is
website_url = fields.String()
should be
website_url = fields.String(allow_none=True)

this causes request to fail request validation in example code

chartmogul_config = chartmogul.Config(chartmogul_API_key)
chartmogul.Customer.all(chartmogul_config, cursor=cursor).get()

modifying field property fixes this issue in chartmogul-4.2.0

Unexpected 'cursor' in response to chartmogul.Activity.all()

I'm getting a TypeError <lambda>() got an unexpected keyword argument 'cursor' after several months of successfully using the same API call req = chartmogul.Activity.all(config, start_date=from_date, per_page=pp)

Have there been any API changes that might be causing this? Is there anything else that I might be missing?

TypeError: __init__() got an unexpected keyword argument 'status' under v1.2.0

Getting below error for chartmogul.Ping.ping(config).get()

pip install chartmogul==1.2.0

Traceback (most recent call last):
  File "chartmogul_util.py", line 11, in <module>
    chartmogul.Ping.ping(config).get()
  File "/Library/Python/2.7/site-packages/promise/promise.py", line 510, in get
    return self._target_settled_value(_raise=True)
  File "/Library/Python/2.7/site-packages/promise/promise.py", line 514, in _target_settled_value
    return self._target()._settled_value(_raise)
  File "/Library/Python/2.7/site-packages/promise/promise.py", line 224, in _settled_value
    reraise(type(raise_val), raise_val, self._traceback)
  File "/Library/Python/2.7/site-packages/promise/promise.py", line 85, in try_catch
    return (handler(*args, **kwargs), None)
  File "/Library/Python/2.7/site-packages/chartmogul/errors.py", line 21, in annotateHTTPError
    raise err
TypeError: __init__() got an unexpected keyword argument 'status'

Invoice.all raises exception

Calling

chartmogul.imp.Invoice.all(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb').get()

for a valid config and existing customer uuid raises a TypeError.

Full stacktrace:

/.venv/lib/python3.5/site-packages/promise/promise.py in get(self, timeout)
    198         elif self.state == self.FULFILLED:
    199             return self.value
--> 200         raise self.reason
    201
    202     def wait(self, timeout=None):

/.venv/lib/python3.5/site-packages/promise/promise.py in call_and_reject(r)
    344             try:
    345                 if callable(failure):
--> 346                     ret.fulfill(failure(r))
    347                 else:
    348                     ret.reject(r)

/.venv/lib/python3.5/site-packages/chartmogul/errors.py in annotateHTTPError(err)
     19         raise_from(APIError(err.response.content), err)
     20     else:
---> 21         raise err

/.venv/lib/python3.5/site-packages/promise/promise.py in call_and_fulfill(v)
    331             try:
    332                 if callable(success):
--> 333                     ret.fulfill(success(v))
    334                 else:
    335                     ret.fulfill(v)

/.venv/lib/python3.5/site-packages/chartmogul/resource.py in _load(cls, response)
     94         if '_root_key' in dir(cls) is not None and cls._root_key in jsonObj:
     95             return cls._many(cls._schema.load(jsonObj[cls._root_key], many=True).data,
---> 96                              **{key: jsonObj[key] for key in LIST_PARAMS if key in jsonObj})
     97         else:
     98             return cls._schema.load(jsonObj).data

TypeError: __new__() got an unexpected keyword argument 'customer_uuid'

TypeError: __init__() got an unexpected keyword argument 'strict'

Hello, a few hours ago I started experiencing issues with your Python wrapper getting this error (when importing chartmogul):

Python transformation failed: __init__() got an unexpected keyword argument 'strict'
Traceback (most recent call last):
File "<string>", line 5, in <module>
File "/usr/local/lib/python3.7/site-packages/chartmogul/__init__.py", line 5, in <module>
from .api.activity import Activity
File "/usr/local/lib/python3.7/site-packages/chartmogul/api/activity.py", line 6, in <module>
class Activity(Resource):
File "/usr/local/lib/python3.7/site-packages/chartmogul/api/activity.py", line 29, in Activity
_schema = _Schema(strict=True)
TypeError: __init__() got an unexpected keyword argument 'strict'
Script failed.

I dug into your code only to find out that marshmallow has updated to 3.0.0 and this causes the issue in your code. I quickly fixed this by installing marshmallow version 2.20.1. Maybe you want to fix that as well.

chartmogul.Invoice.all() - failing because of has_more

I'm running into the same issue as this โ€“ #79

req = chartmogul.Invoice.all(config, per_page=pp)
response = req.get()

The code above produces the error <lambda>() got an unexpected keyword argument 'has_more'

This script has been running perfectly for several months, so it seems like a recent change

ValidationError({0: {'percentage-change': ['Unknown field.']}})

While retrieving the MRR,

chartmogul.Metrics.mrr(
config,
start_date="2021-12-01",
end_date="2022-06-21",
interval="month")

I'm getting this error message:

<Promise at 0x1622921c0 rejected with ValidationError({0: {'percentage-change': ['Unknown field.']}, 1: {'percentage-change': ['Unknown field.']}, 2: {'percentage-change': ['Unknown field.']}, 3: {'percentage-change': ['Unknown field.']}, 4: {'percentage-change': ['Unknown field.']}, 5: {'percentage-change': ['Unknown field.']}, 6: {'percentage-change': ['Unknown field.']}})>

Security Vulnerability in urllib3 Dependency

Description

A security vulnerability has been identified in the urllib3 package used in the chartmogul-python library. The details of this vulnerability can be found here.

Impact:

  • The vulnerability poses potential risks that could affect the security of applications using this library.
  • There are dependency conflicts for users who need to use the most recent version of urllib3. These conflicts hinder the adoption of security patches and improvements provided in the latest urllib3 versions.

Suggested Solution:

  • Review the security advisory linked above.
  • Update the urllib3 dependency to a secure version as recommended.
  • Resolve any compatibility issues that arise from updating urllib3 to ensure smooth integration.
  • Perform necessary tests to ensure compatibility and stability after the update.

chartmogul.Plan.all() doesn't work

I'm trying Plan.all the example straight from the docs:

>>> chartmogul.__version__
'3.1.2'
>>> result = chartmogul.Plan.all(config, per_page=3).get()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "venv/lib/python3.9/site-packages/promise/promise.py", line 512, in get
    return self._target_settled_value(_raise=True)
  File "venv/lib/python3.9/site-packages/promise/promise.py", line 516, in _target_settled_value
    return self._target()._settled_value(_raise)
  File "venv/lib/python3.9/site-packages/promise/promise.py", line 226, in _settled_value
    reraise(type(raise_val), raise_val, self._traceback)
  File "venv/lib/python3.9/site-packages/six.py", line 719, in reraise
    raise value
  File "venv/lib/python3.9/site-packages/promise/promise.py", line 87, in try_catch
    return (handler(*args, **kwargs), None)
  File "venv/lib/python3.9/site-packages/chartmogul/errors.py", line 21, in annotateHTTPError
    raise err
  File "venv/lib/python3.9/site-packages/promise/promise.py", line 87, in try_catch
    return (handler(*args, **kwargs), None)
  File "venv/lib/python3.9/site-packages/chartmogul/resource.py", line 111, in _load
    return cls._loadJSON(jsonObj)
  File "venv/lib/python3.9/site-packages/chartmogul/resource.py", line 117, in _loadJSON
    return cls._many(
TypeError: <lambda>() got an unexpected keyword argument 'has_more'

Seems to happen regardless of whether there actually is more than a single page of data or not. I added querystring terms to just fetch a single plan and it raises the same error.

However, a similar query on another paginated endpoint works fine:

result = chartmogul.Invoice.all(config, per_page=3).get()

Downgrading urllib3 to 1.26.19 Creates Cross Dependency Issues on Installation for ChartMogul SDK

Issue

While using the ChartMogul SDK, we've encountered cross dependency issues during installation due to the requirement to downgrade urllib3 to version 1.26.19 in #96. This version constraint conflicts with other packages and creates compatibility problems for many developers.

Background

The need to downgrade urllib3 stems from compatibility issues with vcr, a library used in two unit tests within the SDK. vcr does not support the latest versions of urllib3, leading to the necessity to lock urllib3 to an older version.

Impact

Downgrading to urllib3 version 1.26.19:

  • Causes conflicts with other packages that require newer versions of urllib3.
  • Complicates the installation and maintenance process for developers relying on the ChartMogul SDK.

Proposed Solution

To maintain security and ensure compatibility with most developer environments, I propose deprecating the unit tests that use vcr in favor of mocking the requests. This change would allow us to upgrade to the latest version of urllib3 without compatibility issues.

Steps to Deprecate vcr in Unit Tests

  • Replace vcr with unittest.mock or similar mocking libraries to simulate the network requests.
  • Ensure all tests pass and validate the correctness of the mocking implementation.
  • Remove the vcr dependency from the project.

Benefits

  • Enhanced security by using the most recent and secure version of urllib3.
  • Improved compatibility with other packages and developer environments.
  • Simplified maintenance and installation process for the ChartMogul SDK.

We believe this solution will provide a more robust and secure experience for all users of the ChartMogul SDK.

Integrate External ID Lookup for Customers in SDK

Description:

Hello ChartMogul Team,

I am currently working with the ChartMogul API and came across a requirement that I believe would significantly enhance the Python SDK's functionality and is required for our specific use case.

Current Situation

As it stands, the ChartMogul API allows retrieving a customer record by external_id using a cURL call, as shown below:

curl -X GET "https://api.chartmogul.com/v1/customers?external_id={stripe_cus_id}" \
-u YOUR_API_KEY:

In Python, this can be accomplished using the requests library:

import requests
from requests.auth import HTTPBasicAuth

url = 'https://api.chartmogul.com/v1/customers'
params = {
    'external_id': 'zeta01'
}

# Make the request
response = requests.get(url, params=params, auth=HTTPBasicAuth(api_key, api_secret))

However, this functionality is not currently available in the Python SDK, which necessitates using a workaround using the requests library, instead of having a built-in method.

Request

I would like to request the integration of the ability to retrieve customers by external_id directly within the ChartMogul Python SDK. This would streamline the process and improve the overall developer experience by aligning the SDK capabilities with those available through direct API calls, and would help us in our specific use-case.

Benefits

  • Improved Efficiency: Developers can retrieve customer data using external_id without writing additional code for HTTP requests.
  • Consistency: Aligns the SDK with the available API capabilities, providing a more unified development experience.
  • Ease of Use: Simplifies the integration process for developers, reducing the need for manual HTTP request handling.

Conclusion

Integrating this feature into the Python SDK would be greatly appreciated and beneficial to many users who rely on the SDK for efficient API interactions. Thank you for considering this request. I look forward to your response.

Best regards,

David

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.