diff --git a/README.md b/README.md index a3336844..3e2a5e99 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,533 @@ # xero-python +[![PyPI version](https://badge.fury.io/py/xero-python.svg)](https://badge.fury.io/py/xero-python) +[![Github forks](https://img.shields.io/github/forks/XeroAPI/xero-python.svg)](https://github.com/XeroAPI/xero-python/network) +[![Github stars](https://img.shields.io/github/stars/XeroAPI/xero-python.svg)](https://github.com/XeroAPI/xero-python/stargazers) +![total downloads](https://ruby-gem-downloads-badge.herokuapp.com/xero-ruby?type=total) -[![image](https://img.shields.io/pypi/v/xero-python.svg)](https://pypi.python.org/pypi/xero-python) +The xero-python SDK makes it easy for developers to access Xero's APIs in their python code, and build robust applications and software using small business & general ledger accounting data. +# Table of Contents +- [API Client documentation](#api-client-documentation) +- [Sample Applications](#sample-applications) +- [Xero Account Requirements](#xero-account-requirements) +- [Installation](#installation) +- [Configuration](#configuration) +- [Authentication](#authentication) +- [Custom Connections](#custom-connections) +- [API Clients](#api-clients) +- [Helper Methods](#helper-methods) +- [Usage Examples](#usage-examples) +- [SDK conventions](#sdk-conventions) +- [Participating in Xero’s developer community](#participating-in-xeros-developer-community) +- [Contributing](#contributing) - +
- +## API Client documentation +This SDK supports full method coverage for the following Xero API sets: - +| API Set | Description | +| --- | --- | +| [`Accounting`](https://xeroapi.github.io/xero-python/v1/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* +| [Assets](https://xeroapi.github.io/xero-python/v1/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | +| [Files](https://xeroapi.github.io/xero-python/v1/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | +| [Projects](https://xeroapi.github.io/xero-python/v1/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | +| [Payroll (AU)](https://xeroapi.github.io/xero-python/v1/payroll_au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | +| [Payroll (UK)](https://xeroapi.github.io/xero-python/v1/payroll_uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | +| [Payroll (NZ)](https://xeroapi.github.io/xero-python/v1/payroll_nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | -Official python SDK for Xero API generated by OpenAPI spec for oAuth 2 +drawing -## Features +
-* XERO API Client with oauth2 token integration. -* Automatic OAuth 2 token refresh before token expiration. -* Class based interface for Xero API endpoints. -* Model classes used to represent API data. -* Currently Supported API sets: +## Sample Applications +Sample apps can get you started quickly with simple auth flows and advanced usage examples. - * [Accounting API](https://developer.xero.com/documentation/api/api-overview) - * [Assets API](https://developer.xero.com/documentation/assets-api/overview) - * [Files API](https://developer.xero.com/documentation/files-api/overview-files) - * [Payroll API (AU)](https://developer.xero.com/documentation/payroll-api/overview) - * [Payroll API (NZ)](https://developer.xero.com/documentation/payroll-api-nz/overview) - * [Payroll API (UK)](https://developer.xero.com/documentation/payroll-api-uk/overview) - * [Projects API](https://developer.xero.com/documentation/projects/overview-projects) +| Sample App | Description | Screenshot | +| --- | --- | --- | +| [`starter-app`](https://github.com/XeroAPI/xero-python-oauth2-starter) | Basic getting started code samples | drawing +| [`full-app`](https://github.com/XeroAPI/xero-python-oauth2-app) | Complete app with more complex examples | drawing +| [`custom-connections-starter`](https://github.com/XeroAPI/xero-python-custom-connections-starter) | Basic app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) for building M2M integrations to a single org | drawing + +
+ +## Xero Account Requirements +- Create a [free Xero user account](https://www.xero.com/us/signup/api/) +- Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application +- Copy the credentials from your API app and store them using a secure ENV variable strategy +- Decide the [neccesary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality + +# Installation +To install this SDK in your project: +``` +pip install xero-python +``` + +--- +## Configuration +```python +# -*- coding: utf-8 -*- +import os +from functools import wraps +from io import BytesIO +from logging.config import dictConfig +from flask import Flask, session +from flask_oauthlib.contrib.client import OAuth, OAuth2Application +from flask_session import Session +from xero_python.accounting import AccountingApi +from xero_python.assets import AssetApi +from xero_python.project import ProjectApi +from xero_python.payrollau import PayrollAuApi +from xero_python.payrolluk import PayrollUkApi +from xero_python.payrollnz import PayrollNzApi +from xero_python.file import FilesApi +from xero_python.api_client import ApiClient, serialize +from xero_python.api_client.configuration import Configuration +from xero_python.api_client.oauth2 import OAuth2Token +from xero_python.exceptions import AccountingBadRequestException, PayrollUkBadRequestException +from xero_python.identity import IdentityApi +from xero_python.utils import getvalue +import logging_settings +from utils import jsonify, serialize_model + +dictConfig(logging_settings.default_settings) + +# configure main flask application +app = Flask(__name__) +app.config.from_object("default_settings") +app.config.from_pyfile("config.py", silent=True) + +if app.config["ENV"] != "production": + # allow oauth2 loop to run over http (used for local testing only) + os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" + +# configure persistent session cache +Session(app) + +# configure flask-oauthlib application +oauth = OAuth(app) +xero = oauth.remote_app( + name="xero", + version="2", + client_id=app.config["CLIENT_ID"], + client_secret=app.config["CLIENT_SECRET"], + endpoint_url="https://api.xero.com/", + authorization_url="https://login.xero.com/identity/connect/authorize", + access_token_url="https://identity.xero.com/connect/token", + refresh_token_url="https://identity.xero.com/connect/token", + scope="offline_access openid profile email accounting.transactions " + "accounting.transactions.read accounting.reports.read " + "accounting.journals.read accounting.settings accounting.settings.read " + "accounting.contacts accounting.contacts.read accounting.attachments " + "accounting.attachments.read assets projects " + "files " + "payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings", +) # type: OAuth2Application + + +# configure xero-python sdk client +api_client = ApiClient( + Configuration( + debug=app.config["DEBUG"], + oauth2_token=OAuth2Token( + client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] + ), + ), + pool_threads=1, +) + +# configure token persistence and exchange point between flask-oauthlib and xero-python +@xero.tokengetter +@api_client.oauth2_token_getter +def obtain_xero_oauth2_token(): + return session.get("token") + +@xero.tokensaver +@api_client.oauth2_token_saver +def store_xero_oauth2_token(token): + session["token"] = token + session.modified = True +``` + +--- +## Authentication +All API requests go through Xero's OAuth 2.0 gateway and require a valid `access_token` to be set on the `client` which appends the `access_token` [JWT](https://jwt.io/) to the header of each request. + +If you are making an API call for the first time: + +1. Send the user to the Xero authorization URL +```python +@app.route("/login") +def login(): + redirect_url = url_for("oauth_callback", _external=True) + session["state"] = app.config["STATE"] + try: + response = xero.authorize(callback_uri=redirect_url, state=session["state"]) + except Exception as e: + print(e) + raise + return response +``` +2. The user will authorize your application and be sent to your `redirect_uri`. This is when and where to check that the returned "state" param matches that which was previously defined. If the "state" params match, calling the oauth library's `authorized_response()` method will swap the temporary auth code for an access token which you can store and use for subsequent API calls. +```python +@app.route("/callback") +def oauth_callback(): + if request.args.get("state") != session["state"]: + return "Error, state doesn't match, no token for you." + try: + response = xero.authorized_response() + except Exception as e: + print(e) + raise + if response is None or response.get("access_token") is None: + return "Access denied: response=%s" % response + store_xero_oauth2_token(response) + return redirect(url_for("index", _external=True)) +``` +3. Call the Xero API like so: +```python +@app.route("/accounting_invoice_read_all") +@xero_token_required +def accounting_invoice_read_all(): + code = get_code_snippet("INVOICES","READ_ALL") + + #[INVOICES:READ_ALL] + xero_tenant_id = get_xero_tenant_id() + accounting_api = AccountingApi(api_client) + + try: + invoices_read = accounting_api.get_invoices( + xero_tenant_id + ) + except AccountingBadRequestException as exception: + output = "Error: " + exception.reason + json = jsonify(exception.error_data) + else: + output = "Total invoices found: {}.".format(len(invoices_read.invoices) + ) + json = serialize_model(invoices_read) + #[/INVOICES:READ_ALL] + + return render_template( + "output.html", title="Invoices",code=code, output=output, json=json, len = 0, set="accounting", endpoint="invoice", action="read_all" + ) +``` + +It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK `client`, and refresh the `access_token` prior to making API calls. + +### Token Set +| key | value | description | +| --- | --- | --- | +| id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | +| access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | +| expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | +| refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | +| scope: | "email profile openid accounting.transactions offline_access" | The Xero permissions that are embedded in the `access_token` | + +Example Token Set JSON: +``` +{ + "id_token": "xxx.yyy.zz", + "access_token": "xxx.yyy.zzz", + "expires_in": 1800, + "token_type": "Bearer", + "refresh_token": "xxxxxxxxx", + "scope": "email profile openid accounting.transactions offline_access" +} +``` + +--- +## Custom Connections + +Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth 2.0's [`client_credentials`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. + +To use this SDK with a Custom Connections: +```python +# -*- coding: utf-8 -*- +import os +from functools import wraps +from io import BytesIO +from logging.config import dictConfig +from flask import Flask, url_for, render_template, session, redirect, json, send_file +from flask_session import Session +from xero_python.accounting import AccountingApi, ContactPerson, Contact, Contacts +from xero_python.api_client import ApiClient, serialize +from xero_python.api_client.configuration import Configuration +from xero_python.api_client.oauth2 import OAuth2Token +from xero_python.exceptions import AccountingBadRequestException +from xero_python.identity import IdentityApi +from xero_python.utils import getvalue +import logging_settings +from utils import jsonify, serialize_model + +dictConfig(logging_settings.default_settings) + +# configure main flask application +app = Flask(__name__) +app.config.from_object("default_settings") +app.config.from_pyfile("config.py", silent=True) + +# configure persistent session cache +Session(app) + +# configure xero-python sdk client +api_client = ApiClient( + Configuration( + debug=app.config["DEBUG"], + oauth2_token=OAuth2Token( + client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] + ), + ), + pool_threads=1, +) + +# configure token persistence and exchange point between app session and xero-python +@api_client.oauth2_token_getter +def obtain_xero_oauth2_token(): + return session.get("token") + +@api_client.oauth2_token_saver +def store_xero_oauth2_token(token): + session["token"] = token + session.modified = True + +@app.route("/get_token") +def get_token(): + try: + # no user auth flow, no exchanging temp code for token + xero_token = api_client.get_client_credentials_token() + except Exception as e: + print(e) + raise + # todo validate state value + if xero_token is None or xero_token.get("access_token") is None: + return "Access denied: response=%s" % xero_token + store_xero_oauth2_token(xero_token) + return redirect(url_for("index", _external=True)) + +@app.route("/accounting_invoice_read_all") +@xero_token_required +def accounting_invoice_read_all(): + code = get_code_snippet("INVOICES","READ_ALL") + + #[INVOICES:READ_ALL] + accounting_api = AccountingApi(api_client) + + try: + invoices_read = accounting_api.get_invoices('') + except AccountingBadRequestException as exception: + output = "Error: " + exception.reason + json = jsonify(exception.error_data) + else: + output = "Total invoices found: {}.".format(len(invoices_read.invoices) + ) + json = serialize_model(invoices_read) + #[/INVOICES:READ_ALL] + + return render_template( + "output.html", title="Invoices",code=code, output=output, json=json, len = 0, set="accounting", endpoint="invoice", action="read_all" + ) +``` + +Because Custom Connections are only valid for a single organisation you don't need to pass the xero-tenant-id as the first parameter to every method, or more specifically for this SDK xeroTenantId can be an empty string. + +> Because the SDK is generated from the OpenAPI spec the parameter remains. For now you are required to pass an empty string to use this SDK with a Custom Connection. + +--- +## API Clients +You can access the different API sets and their available methods through the following: + +```python +accounting_api = AccountingApi(api_client) +read_accounts = accounting_api.get_accounts(xero_tenant_id) +asset_api = AssetApi(api_client) +read_assets = asset_api.get_assets(xero_tenant_id) +# ... all the API sets follow the same pattern +``` +--- +## Helper Methods + +Once you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a new `client` and refresh the token set. + +```python +# configure xero-python sdk client +api_client = ApiClient( + Configuration( + debug=app.config["DEBUG"], + oauth2_token=OAuth2Token( + client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] + ), + ), + pool_threads=1, +) + +# configure token persistence and exchange point between app session and xero-python +@api_client.oauth2_token_getter +def obtain_xero_oauth2_token(): + return session.get("token") + +@api_client.oauth2_token_saver +def store_xero_oauth2_token(token): + session["token"] = token + session.modified = True + +# get existing token set +token_set = get_token_set_from_database(user_id); // example function name + +# set token set to the api client +store_xero_oauth2_token(token_set) + +# refresh token set on the api client +api_client.refresh_oauth2_token() + +# call the Xero API +accounting_api = AccountingApi(api_client) +read_accounts = accounting_api.get_accounts(xero_tenant_id) +``` + +A full list of the SDK client's methods: + +| method | description | params | returns +| --- | --- | --- | --- | +| api_client.`oauth2_token_saver` | A decorator to register a callback function for saving refreshed token while the old token has expired | token_saver: the callback function accepting `token` argument | token_saver to allow this method be used as decorator | +| api_client.`oauth2_token_getter` | A decorator to register a callback function for getting oauth2 token | token_getter: the callback function returning oauth2 token dictionary | token_getter to allow this method be used as decorator | +| api_client.`revoke_oauth2_token` | Revokes a users refresh token and removes all their connections to your app | N/A | empty OAuth2 token | +| api_client.`refresh_oauth2_token` | Refreshes OAuth2 token set | N/A | new token set | +| api_client.`set_oauth2_token` | Sets OAuth2 token directly on the client | dict token: standard token dictionary | N/A | +| api_client.`get_oauth2_token` | Get OAuth2 token dictionary | N/A | dict | +--- +## Usage Examples +### Accounting API +```python +from xero_python.accounting import AccountingApi +from xero_python.utils import getvalue + +accounting_api = AccountingApi(api_client) + +# Get Accounts +read_accounts = accounting_api.get_accounts(xero_tenant_id) +account_id = getvalue(read_accounts, "accounts.0.account_id", "") + +# Get Account by ID +read_one_account = accounting_api.get_account(xero_tenant_id, account_id) + +# Create Invoice +# get contact +read_contacts = accounting_api.get_contacts(xero_tenant_id) +contact_id = getvalue(read_contacts, "contacts.0.contact_id", "") +# get account +where = "Type==\"SALES\"&&Status==\"ACTIVE\"" +read_accounts = accounting_api.get_accounts( + xero_tenant_id, where=where +) +account_id = getvalue(read_accounts, "accounts.0.account_id", "") +# build Invoices +contact = Contact( + contact_id=contact_id +) +line_item = LineItem( + account_code=account_id, + description= "Consulting", + quantity=1.0, + unit_amount=10.0, +) +invoice = Invoice( + line_items=[line_item], + contact=contact, + due_date= dateutil.parser.parse("2020-09-03T00:00:00Z"), + date= dateutil.parser.parse("2020-07-03T00:00:00Z"), + type="ACCREC" +) +invoices = Invoices(invoices=[invoice]) +created_invoices = accounting_api.create_invoices(xero_tenant_id, invoices=invoices) +invoice_id = getvalue(read_invoices, "invoices.0.invoice_id", "") + +# Create Attachment +include_online = True +file_name = "helo-heros.jpg" +path_to_upload = Path(__file__).resolve().parent.joinpath(file_name) +open_file = open(path_to_upload, 'rb') +body = open_file.read() +content_type = mimetypes.MimeTypes().guess_type(file_name)[0] + +created_invoice_attachments_by_file_name = accounting_api.create_invoice_attachment_by_file_name( + xero_tenant_id, + invoice_id, + file_name, + body, + include_online, +) +``` + +--- +## SDK conventions + +### Querying & Filtering + +Describe the support for query options and filtering + +```python +# configure api_client for use with xero-python sdk client +api_client = ApiClient( + Configuration( + debug=false, + oauth2_token=OAuth2Token( + client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET" + ), + ), + pool_threads=1, +) + +api_client.set_oauth2_token("YOUR_ACCESS_TOKEN") + +def accounting_get_invoices(): + api_instance = AccountingApi(api_client) + xero_tenant_id = 'YOUR_XERO_TENANT_ID' + if_modified_since = dateutil.parser.parse("2020-02-06T12:17:43.202-08:00") + where = 'Status=="DRAFT"' + order = 'InvoiceNumber ASC' + ids = ["00000000-0000-0000-0000-000000000000"] + invoice_numbers = ["INV-001", "INV-002"] + contact_ids = ["00000000-0000-0000-0000-000000000000"] + statuses = ["DRAFT", "SUBMITTED"] + include_archived = 'true' + created_by_my_app = 'false' + summary_only = 'true' -* Error handling for ease of use. - -## SDK Documentation -* [Accounting](https://xeroapi.github.io/xero-python/v1/accounting/index.html) - -## Starter Project -We've created [xero-python-outh2-starter](https://github.com/XeroAPI/xero-python-oauth2-starter) a project to demonstrate how to use this SDK. - -* oauth 2 flow to obtain a token -* token refresh -* identity to obtain tenant_id -* organisation endpoint -* create contact -* create multiple contacts -* get invoices using where clause - -Here is a [15 min video walkthrough](https://www.youtube.com/watch?v=i8JWtbMo90M) on using the starter project. - -## Kitchen Sync app -We've created [xero-python-outh2-app](https://github.com/XeroAPI/xero-python-oauth2-app) a project to demonstrate how to make API calls and displays the python code used to make the call and the JSON response. - -* oauth 2 flow to obtain a token -* token refresh -* identity to obtain tenant_id -* accounting - * accounts - * contacts - * invoices -* assets - * asset - * asset type - * asset settings -* projects - * projects - * project users - * tasks - * time -* au payroll - * employee - * leave applications - * pay items - * payroll calendar - * pay runs - * pay slips - * settings - * superfunds - * superfund products - * timesheets -* uk payroll - * employees - * employement - * employees tax - * employee opening balance - * employees leave - * employees leave balances - * employees statutory leave balances - * employees statutory leave summary - * employees statutory sick leave - * employees leave periods - * employees leave types - * employees pay templates - * employer pensions - * deductions - * earnings orders - * earnings rates - * leave types - * reimbursements - * timesheets - * payment methods - * payrun calendars - * salary and wage - * pay runs - * pay slips - * settings - * tracking categories - -## Credits - -* This package was created with -[Cookiecutter](https://github.com/audreyr/cookiecutter) and the -[audreyr/cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage) -project template. +api_response = api_instance.get_invoices( + xero_tenant_id, + if_modified_since, + where, + order, + ids, + invoice_numbers, + contact_ids, + statuses, + page, + include_archived, + created_by_my_app, + unitdp, + summary_only +) +``` + +--- ## Participating in Xero’s developer community + This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/xero-python/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it -* If you raise an issue then please make sure to fill out the github issue template, doing so helps us help you +* If you raise an issue then please make sure to fill out the Github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/xero-python/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/xero-python/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/). + +### Contributing +PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. + +### Versioning +We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know. \ No newline at end of file diff --git a/docs/v1/accounting/index.html b/docs/v1/accounting/index.html index 90a95771..459219a9 100644 --- a/docs/v1/accounting/index.html +++ b/docs/v1/accounting/index.html @@ -1,173 +1,244 @@ - - - Xero Accounting API - - - - - - xero-python Accounting SDK Docs - - - - - - - - - - - - - - + + + + + + + + - - - - -
- -
- -
-
- - -
-
- -
-
- + function routeDocs(event) { + var selectedOption = event.options[event.selectedIndex]; + location = selectedOption.dataset.url + } + +
+ +
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
-
-
-
-
- -
-
-
-
-

Xero Accounting API

-
-
+
+
+
+

Xero Accounting API

- +
- - - - if (this.schema['enum']) { - var tempDiv = document.createElement('span');; - tempDiv.classList.add('inner'); - tempDiv.innerHTML = '' + this.schema['enum'].join(', ') + ''; - element.querySelector('.enums.inner').appendChild(tempDiv); - } + + - + }, {}] + }, {}, [3])(3) + }); + - - - - - - - - + return replacer == null ? value : replacer.call(this, key, value) + } + } + + + + + + + + + \ No newline at end of file diff --git a/setup.py b/setup.py index e26b5ab8..fff2549e 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ def read_file(filename): keywords="xero python sdk API oAuth", name="xero_python", packages=find_packages(include=["xero_python", "xero_python.*"]), - version="1.7.0", + version="1.8.0", ) diff --git a/xero_python/__init__.py b/xero_python/__init__.py index 504d31fb..45088345 100644 --- a/xero_python/__init__.py +++ b/xero_python/__init__.py @@ -2,4 +2,4 @@ __author__ = """Xero Developer API""" __email__ = "api@xero.com" -__version__ = "1.7.0" +__version__ = "1.8.0" diff --git a/xero_python/accounting/__init__.py b/xero_python/accounting/__init__.py index 7b4c08a9..4fcc0c14 100644 --- a/xero_python/accounting/__init__.py +++ b/xero_python/accounting/__init__.py @@ -45,7 +45,8 @@ from xero_python.accounting.models.branding_theme import BrandingTheme from xero_python.accounting.models.branding_themes import BrandingThemes from xero_python.accounting.models.budget import Budget -from xero_python.accounting.models.budget_lines import BudgetLines +from xero_python.accounting.models.budget_balance import BudgetBalance +from xero_python.accounting.models.budget_line import BudgetLine from xero_python.accounting.models.budgets import Budgets from xero_python.accounting.models.cis_org_setting import CISOrgSetting from xero_python.accounting.models.cis_org_settings import CISOrgSettings diff --git a/xero_python/accounting/api/accounting_api.py b/xero_python/accounting/api/accounting_api.py index 7c3ffe2b..f1658e79 100644 --- a/xero_python/accounting/api/accounting_api.py +++ b/xero_python/accounting/api/accounting_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/accounting/docs/Budget.md b/xero_python/accounting/docs/Budget.md index 86cef5d2..c5583969 100644 --- a/xero_python/accounting/docs/Budget.md +++ b/xero_python/accounting/docs/Budget.md @@ -7,8 +7,8 @@ Name | Type | Description | Notes **type** | **str** | Type of Budget. OVERALL or TRACKING | [optional] **description** | **str** | The Budget description | [optional] **updated_date_utc** | **datetime** | UTC timestamp of last update to budget | [optional] -**budget_lines** | [**BudgetLines**](BudgetLines.md) | | [optional] -**tracking** | [**TrackingCategory**](TrackingCategory.md) | | [optional] +**budget_lines** | [**list[BudgetLine]**](BudgetLine.md) | | [optional] +**tracking** | [**list[TrackingCategory]**](TrackingCategory.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/xero_python/accounting/docs/BudgetBalance.md b/xero_python/accounting/docs/BudgetBalance.md new file mode 100644 index 00000000..74b082ea --- /dev/null +++ b/xero_python/accounting/docs/BudgetBalance.md @@ -0,0 +1,13 @@ +# BudgetBalance + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**period** | **date** | Period the amount applies to (e.g. “2019-08”) | [optional] +**amount** | **int** | LineItem Quantity | [optional] +**unit_amount** | **int** | Budgeted amount | [optional] +**notes** | **str** | Any footnotes associated with this balance | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/xero_python/accounting/docs/BudgetLine.md b/xero_python/accounting/docs/BudgetLine.md new file mode 100644 index 00000000..3bc2c61d --- /dev/null +++ b/xero_python/accounting/docs/BudgetLine.md @@ -0,0 +1,12 @@ +# BudgetLine + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**account_id** | **str** | See Accounts | [optional] +**account_code** | **str** | See Accounts | [optional] +**budget_balances** | [**list[BudgetBalance]**](BudgetBalance.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/xero_python/accounting/models/__init__.py b/xero_python/accounting/models/__init__.py index 06ed879e..a529bddf 100644 --- a/xero_python/accounting/models/__init__.py +++ b/xero_python/accounting/models/__init__.py @@ -40,7 +40,8 @@ from xero_python.accounting.models.branding_theme import BrandingTheme from xero_python.accounting.models.branding_themes import BrandingThemes from xero_python.accounting.models.budget import Budget -from xero_python.accounting.models.budget_lines import BudgetLines +from xero_python.accounting.models.budget_balance import BudgetBalance +from xero_python.accounting.models.budget_line import BudgetLine from xero_python.accounting.models.budgets import Budgets from xero_python.accounting.models.cis_org_setting import CISOrgSetting from xero_python.accounting.models.cis_org_settings import CISOrgSettings diff --git a/xero_python/accounting/models/budget.py b/xero_python/accounting/models/budget.py index 763263f3..d1c1db28 100644 --- a/xero_python/accounting/models/budget.py +++ b/xero_python/accounting/models/budget.py @@ -34,8 +34,8 @@ class Budget(BaseModel): "type": "str", "description": "str", "updated_date_utc": "datetime[ms-format]", - "budget_lines": "BudgetLines", - "tracking": "TrackingCategory", + "budget_lines": "list[BudgetLine]", + "tracking": "list[TrackingCategory]", } attribute_map = { @@ -191,7 +191,7 @@ def budget_lines(self): :return: The budget_lines of this Budget. # noqa: E501 - :rtype: BudgetLines + :rtype: list[BudgetLine] """ return self._budget_lines @@ -201,7 +201,7 @@ def budget_lines(self, budget_lines): :param budget_lines: The budget_lines of this Budget. # noqa: E501 - :type: BudgetLines + :type: list[BudgetLine] """ self._budget_lines = budget_lines @@ -212,7 +212,7 @@ def tracking(self): :return: The tracking of this Budget. # noqa: E501 - :rtype: TrackingCategory + :rtype: list[TrackingCategory] """ return self._tracking @@ -222,7 +222,7 @@ def tracking(self, tracking): :param tracking: The tracking of this Budget. # noqa: E501 - :type: TrackingCategory + :type: list[TrackingCategory] """ self._tracking = tracking diff --git a/xero_python/accounting/models/budget_balance.py b/xero_python/accounting/models/budget_balance.py new file mode 100644 index 00000000..af7b2056 --- /dev/null +++ b/xero_python/accounting/models/budget_balance.py @@ -0,0 +1,161 @@ +# coding: utf-8 + +""" + Xero Accounting API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + Contact: api@xero.com + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 + +from xero_python.models import BaseModel + + +class BudgetBalance(BaseModel): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + "period": "date[ms-format]", + "amount": "int", + "unit_amount": "int", + "notes": "str", + } + + attribute_map = { + "period": "Period", + "amount": "Amount", + "unit_amount": "UnitAmount", + "notes": "Notes", + } + + def __init__( + self, period=None, amount=None, unit_amount=None, notes=None + ): # noqa: E501 + """BudgetBalance - a model defined in OpenAPI""" # noqa: E501 + + self._period = None + self._amount = None + self._unit_amount = None + self._notes = None + self.discriminator = None + + if period is not None: + self.period = period + if amount is not None: + self.amount = amount + if unit_amount is not None: + self.unit_amount = unit_amount + if notes is not None: + self.notes = notes + + @property + def period(self): + """Gets the period of this BudgetBalance. # noqa: E501 + + Period the amount applies to (e.g. “2019-08”) # noqa: E501 + + :return: The period of this BudgetBalance. # noqa: E501 + :rtype: date + """ + return self._period + + @period.setter + def period(self, period): + """Sets the period of this BudgetBalance. + + Period the amount applies to (e.g. “2019-08”) # noqa: E501 + + :param period: The period of this BudgetBalance. # noqa: E501 + :type: date + """ + + self._period = period + + @property + def amount(self): + """Gets the amount of this BudgetBalance. # noqa: E501 + + LineItem Quantity # noqa: E501 + + :return: The amount of this BudgetBalance. # noqa: E501 + :rtype: int + """ + return self._amount + + @amount.setter + def amount(self, amount): + """Sets the amount of this BudgetBalance. + + LineItem Quantity # noqa: E501 + + :param amount: The amount of this BudgetBalance. # noqa: E501 + :type: int + """ + + self._amount = amount + + @property + def unit_amount(self): + """Gets the unit_amount of this BudgetBalance. # noqa: E501 + + Budgeted amount # noqa: E501 + + :return: The unit_amount of this BudgetBalance. # noqa: E501 + :rtype: int + """ + return self._unit_amount + + @unit_amount.setter + def unit_amount(self, unit_amount): + """Sets the unit_amount of this BudgetBalance. + + Budgeted amount # noqa: E501 + + :param unit_amount: The unit_amount of this BudgetBalance. # noqa: E501 + :type: int + """ + + self._unit_amount = unit_amount + + @property + def notes(self): + """Gets the notes of this BudgetBalance. # noqa: E501 + + Any footnotes associated with this balance # noqa: E501 + + :return: The notes of this BudgetBalance. # noqa: E501 + :rtype: str + """ + return self._notes + + @notes.setter + def notes(self, notes): + """Sets the notes of this BudgetBalance. + + Any footnotes associated with this balance # noqa: E501 + + :param notes: The notes of this BudgetBalance. # noqa: E501 + :type: str + """ + if notes is not None and len(notes) > 255: + raise ValueError( + "Invalid value for `notes`, " + "length must be less than or equal to `255`" + ) # noqa: E501 + + self._notes = notes diff --git a/xero_python/accounting/models/budget_line.py b/xero_python/accounting/models/budget_line.py new file mode 100644 index 00000000..0c311919 --- /dev/null +++ b/xero_python/accounting/models/budget_line.py @@ -0,0 +1,126 @@ +# coding: utf-8 + +""" + Xero Accounting API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501 + + Contact: api@xero.com + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 + +from xero_python.models import BaseModel + + +class BudgetLine(BaseModel): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + "account_id": "str", + "account_code": "str", + "budget_balances": "list[BudgetBalance]", + } + + attribute_map = { + "account_id": "AccountID", + "account_code": "AccountCode", + "budget_balances": "BudgetBalances", + } + + def __init__( + self, account_id=None, account_code=None, budget_balances=None + ): # noqa: E501 + """BudgetLine - a model defined in OpenAPI""" # noqa: E501 + + self._account_id = None + self._account_code = None + self._budget_balances = None + self.discriminator = None + + if account_id is not None: + self.account_id = account_id + if account_code is not None: + self.account_code = account_code + if budget_balances is not None: + self.budget_balances = budget_balances + + @property + def account_id(self): + """Gets the account_id of this BudgetLine. # noqa: E501 + + See Accounts # noqa: E501 + + :return: The account_id of this BudgetLine. # noqa: E501 + :rtype: str + """ + return self._account_id + + @account_id.setter + def account_id(self, account_id): + """Sets the account_id of this BudgetLine. + + See Accounts # noqa: E501 + + :param account_id: The account_id of this BudgetLine. # noqa: E501 + :type: str + """ + + self._account_id = account_id + + @property + def account_code(self): + """Gets the account_code of this BudgetLine. # noqa: E501 + + See Accounts # noqa: E501 + + :return: The account_code of this BudgetLine. # noqa: E501 + :rtype: str + """ + return self._account_code + + @account_code.setter + def account_code(self, account_code): + """Sets the account_code of this BudgetLine. + + See Accounts # noqa: E501 + + :param account_code: The account_code of this BudgetLine. # noqa: E501 + :type: str + """ + + self._account_code = account_code + + @property + def budget_balances(self): + """Gets the budget_balances of this BudgetLine. # noqa: E501 + + + :return: The budget_balances of this BudgetLine. # noqa: E501 + :rtype: list[BudgetBalance] + """ + return self._budget_balances + + @budget_balances.setter + def budget_balances(self, budget_balances): + """Sets the budget_balances of this BudgetLine. + + + :param budget_balances: The budget_balances of this BudgetLine. # noqa: E501 + :type: list[BudgetBalance] + """ + + self._budget_balances = budget_balances diff --git a/xero_python/api_client/__init__.py b/xero_python/api_client/__init__.py index 22b8f792..69a5e17e 100644 --- a/xero_python/api_client/__init__.py +++ b/xero_python/api_client/__init__.py @@ -758,6 +758,15 @@ def revoke_oauth2_token(self): if oauth2_token.revoke_access_token(self): return self.get_oauth2_token() + def get_client_credentials_token(self): + """ + Obtain oauth2 token using client credentials grant type + :return: oauth2 token + """ + oauth2_token = self.configuration.oauth2_token + if oauth2_token.get_client_credentials_access_token(self): + return self.get_oauth2_token() + def oauth2_token_getter(self, token_getter): """ A decorator to register a callback function for getting oauth2 token diff --git a/xero_python/api_client/oauth2.py b/xero_python/api_client/oauth2.py index 7f7e41a5..b7543ce3 100644 --- a/xero_python/api_client/oauth2.py +++ b/xero_python/api_client/oauth2.py @@ -10,6 +10,7 @@ class TokenApi: Api class handles interactions with xero token API endpoints """ + client_credentials_token_url = "https://identity.xero.com/connect/token" refresh_token_url = "https://identity.xero.com/connect/token" revoke_token_url = "https://identity.xero.com/connect/revocation" @@ -80,6 +81,35 @@ def revoke_token(self, refresh_token): ) return status + def get_client_credentials_token(self): + """ + Call Xero Identity API to obtain an access token via OAuth2 Client Credentails grant type + :return: dictionary with new auth2 token + """ + post_data = { + "client_id": self.client_id, + "client_secret": self.client_secret, + "grant_type": "client_credentials", + } + response, status, headers = self.api_client.call_api( + self.client_credentials_token_url, + "POST", + header_params={ + "Accept": "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }, + post_params=post_data, + auth_settings=None, # important to prevent infinite recursive loop + _preload_content=False, + ) + if status != 200: + # todo improve error handling + raise Exception( + "refresh token status {} {} {!r}".format(status, response, headers) + ) + # todo validate response is json + return self.parse_token_response(response) + def parse_token_response(self, response): """ Parse token data from http response @@ -201,6 +231,18 @@ def refresh_access_token(self, api_client): api_client.set_oauth2_token(new_token) return True + def get_client_credentials_access_token(self, api_client): + """ + Perform OAuth2 Client Credentials grant token request. + :param api_client: ApiClient instance used to perform refresh token API call. + :return: bool - True if success + """ + token_api = TokenApi(api_client, self.client_id, self.client_secret) + new_token = token_api.get_client_credentials_token() + self.update_token(**new_token) + api_client.set_oauth2_token(new_token) + return True + def revoke_access_token(self, api_client): """ Perform auth2 revoke token call. @@ -228,19 +270,19 @@ def revoke_access_token(self, api_client): def update_token( self, access_token, - refresh_token, scope, - expires_at, expires_in, token_type, + expires_at=None, + refresh_token=None, id_token=None, ): """ Set new auth2 token details :param access_token: str - :param refresh_token: str + :param refresh_token: str (optional) :param scope: list of strings - :param expires_at: float timestamp + :param expires_at: float timestamp (optioanl) :param expires_in: number :param token_type: str :param id_token: str (optional) diff --git a/xero_python/assets/api/asset_api.py b/xero_python/assets/api/asset_api.py index 28977746..d6dcd2dd 100644 --- a/xero_python/assets/api/asset_api.py +++ b/xero_python/assets/api/asset_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/docs/README.md b/xero_python/docs/README.md index fe246a7c..a90307b4 100644 --- a/xero_python/docs/README.md +++ b/xero_python/docs/README.md @@ -3,8 +3,8 @@ These endpoints are related to managing authentication tokens and identity for X The `xero_python` package is automatically generated by the [XeroAPI SDK 2.0 Codegen](https://github.com/xero-github/xeroapi-sdk-codegen) project: -- API version: 2.13.2 -- Package version: 1.7.0 +- API version: 2.13.4 +- Package version: 1.8.0 - Build package: org.openapitools.codegen.languages.PythonClientCodegen For more information, please visit [https://developer.xero.com](https://developer.xero.com) diff --git a/xero_python/file/api/files_api.py b/xero_python/file/api/files_api.py index 3f983c6e..e46da77b 100644 --- a/xero_python/file/api/files_api.py +++ b/xero_python/file/api/files_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/identity/api/identity_api.py b/xero_python/identity/api/identity_api.py index 3b67b253..23ad10ec 100644 --- a/xero_python/identity/api/identity_api.py +++ b/xero_python/identity/api/identity_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/payrollau/api/payroll_au_api.py b/xero_python/payrollau/api/payroll_au_api.py index c42a2ec1..2dc21a78 100644 --- a/xero_python/payrollau/api/payroll_au_api.py +++ b/xero_python/payrollau/api/payroll_au_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/payrollau/docs/PayrollCalendar.md b/xero_python/payrollau/docs/PayrollCalendar.md index 1cfb0ed1..389c25a6 100644 --- a/xero_python/payrollau/docs/PayrollCalendar.md +++ b/xero_python/payrollau/docs/PayrollCalendar.md @@ -9,6 +9,7 @@ Name | Type | Description | Notes **payment_date** | **date** | The date on which employees will be paid for the upcoming pay period (YYYY-MM-DD) | [optional] **payroll_calendar_id** | **str** | Xero identifier | [optional] **updated_date_utc** | **datetime** | Last modified timestamp | [optional] +**reference_date** | **date** | Reference Date (YYYY-MM-DD) | [optional] **validation_errors** | [**list[ValidationError]**](ValidationError.md) | Displays array of validation error messages from the API | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/xero_python/payrollau/models/payroll_calendar.py b/xero_python/payrollau/models/payroll_calendar.py index 32477d9e..8ae75b2e 100644 --- a/xero_python/payrollau/models/payroll_calendar.py +++ b/xero_python/payrollau/models/payroll_calendar.py @@ -36,6 +36,7 @@ class PayrollCalendar(BaseModel): "payment_date": "date[ms-format]", "payroll_calendar_id": "str", "updated_date_utc": "datetime[ms-format]", + "reference_date": "date[ms-format]", "validation_errors": "list[ValidationError]", } @@ -46,6 +47,7 @@ class PayrollCalendar(BaseModel): "payment_date": "PaymentDate", "payroll_calendar_id": "PayrollCalendarID", "updated_date_utc": "UpdatedDateUTC", + "reference_date": "ReferenceDate", "validation_errors": "ValidationErrors", } @@ -57,6 +59,7 @@ def __init__( payment_date=None, payroll_calendar_id=None, updated_date_utc=None, + reference_date=None, validation_errors=None, ): # noqa: E501 """PayrollCalendar - a model defined in OpenAPI""" # noqa: E501 @@ -67,6 +70,7 @@ def __init__( self._payment_date = None self._payroll_calendar_id = None self._updated_date_utc = None + self._reference_date = None self._validation_errors = None self.discriminator = None @@ -82,6 +86,8 @@ def __init__( self.payroll_calendar_id = payroll_calendar_id if updated_date_utc is not None: self.updated_date_utc = updated_date_utc + if reference_date is not None: + self.reference_date = reference_date if validation_errors is not None: self.validation_errors = validation_errors @@ -221,6 +227,29 @@ def updated_date_utc(self, updated_date_utc): self._updated_date_utc = updated_date_utc + @property + def reference_date(self): + """Gets the reference_date of this PayrollCalendar. # noqa: E501 + + Reference Date (YYYY-MM-DD) # noqa: E501 + + :return: The reference_date of this PayrollCalendar. # noqa: E501 + :rtype: date + """ + return self._reference_date + + @reference_date.setter + def reference_date(self, reference_date): + """Sets the reference_date of this PayrollCalendar. + + Reference Date (YYYY-MM-DD) # noqa: E501 + + :param reference_date: The reference_date of this PayrollCalendar. # noqa: E501 + :type: date + """ + + self._reference_date = reference_date + @property def validation_errors(self): """Gets the validation_errors of this PayrollCalendar. # noqa: E501 diff --git a/xero_python/payrollnz/api/payroll_nz_api.py b/xero_python/payrollnz/api/payroll_nz_api.py index 66142fe5..353ed948 100644 --- a/xero_python/payrollnz/api/payroll_nz_api.py +++ b/xero_python/payrollnz/api/payroll_nz_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/payrolluk/api/payroll_uk_api.py b/xero_python/payrolluk/api/payroll_uk_api.py index a9bdf00d..981229d6 100644 --- a/xero_python/payrolluk/api/payroll_uk_api.py +++ b/xero_python/payrolluk/api/payroll_uk_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib diff --git a/xero_python/project/api/project_api.py b/xero_python/project/api/project_api.py index 6c6ef3f2..bd6aad97 100644 --- a/xero_python/project/api/project_api.py +++ b/xero_python/project/api/project_api.py @@ -10,7 +10,7 @@ """ """ - OpenAPI spec version: 2.13.2 + OpenAPI spec version: 2.13.4 """ import importlib