用于访问Xero会计工具REST API的Python API。
项目描述
PyXero
PyXero是访问由Xero会计工具提供的REST API的Python API。它允许访问公共、私有和合作伙伴应用程序。
快速入门
使用python包管理器安装此库
pip install pyxero
使用OAuth2凭证
OAuth2是一个开放标准的授权协议,允许用户为想要使用其帐户的应用程序提供特定权限。OAuth2身份验证是通过使用API获得的令牌来执行的;然后这些令牌将随后续请求一起提供。
OAuth2令牌的有效期为30分钟,但可以在任何时候交换为新令牌。Xero关于OAuth2流程的文档可以在这里找到。创建和验证凭证的流程如下 (最后有Django示例)
-
使用将由您的应用程序提供的重定向URI注册您的应用程序与Xero,例如
https://mysite.com/oauth/xero/callback/
。请参阅第3步了解您的应用程序应该做什么。生成客户端密钥,然后将其存储在应用程序可以访问的地方,例如配置文件中。 -
使用第一步中的详细信息构建一个
OAuth2Credentials
实例。>>> from xero.auth import OAuth2Credentials >>> >>> credentials = OAuth2Credentials(client_id, client_secret, >>> callback_uri=callback_uri)
如有必要,请传入一个作用域列表以定义您的应用所需的作用域。例如,如果需要写入访问交易和薪酬员工
>>> from xero.constants import XeroScopes >>> >>> my_scope = [XeroScopes.ACCOUNTING_TRANSACTIONS, >>> XeroScopes.PAYROLL_EMPLOYEES] >>> credentials = OAuth2Credentials(client_id, client_secret, scope=my_scope >>> callback_uri=callback_uri)
默认作用域为
['offline_access', 'accounting.transactions.read', 'accounting.contacts.read']
。offline_access
是必需的,以便令牌可刷新。有关作用域的更多详细信息,请参阅Xero的 文档。 -
生成一个Xero授权URL,用户可以访问以完成授权。然后存储凭据对象的state状态并将用户重定向到浏览器中的URL。
>>> authorisation_url = credentials.generate_url() >>> >>> # Now store credentials.state somewhere accessible, e.g a cache >>> mycache['xero_creds'] = credentials.state >>> >>> # then redirect the user to authorisation_url ...
回调URI应该是步骤1中使用的重定向URI。
-
授权后,用户将从Xero重定向到步骤1中提供的回调URI,并附带包含认证秘密的查询字符串。当您的应用处理此请求时,它应该将包括查询字符串在内的完整URI传递给
verify()
>>> # Recreate the credentials object >>> credentials = OAuth2Credentials(**mycache['xero_creds']) >>> >>> # Get the full redirect uri from the request including querystring >>> # e.g. request_uri = 'https://mysite.com/oauth/xero/callback/?code=0123456789&scope=openid%20profile&state=87784234sdf5ds8ad546a8sd545ss6' >>> >>> credentials.verify(request_uri)
将从Xero获取令牌并将其保存为
credentials.token
。如果需要重新创建凭据对象,可以使用>>> cred_state = credentials.state >>> ... >>> new_creds = OAuth2Credentials(**cred_state)
或者只需使用client_id、client_secret和token(可选作用域和tenant_id)
>>> token = credentials.token >>> ... >>> new_creds = OAuth2Credentials(client_id, client_secret, token=token)
-
现在可以使用凭据授权Xero会话。由于OAuth2允许对多个Xero组织进行身份验证,因此必须设置xero客户端查询将运行的tenant_id。
>>> from xero import Xero >>> # Use the first xero organisation (tenant) permitted >>> credentials.set_default_tenant() >>> xero = Xero(credentials) >>> xero.contacts.all() >>> ...
如果步骤2中提供的作用域不需要访问组织(例如,仅请求单点登录的作用域),则无法使用Xero API进行请求,并将引发
set_default_tenant()
异常。要选择多个可能的Xero组织,可以显式设置
tenant_id
>>> tenants = credentials.get_tenants() >>> credentials.tenant_id = tenants[1]['tenantId'] >>> xero = Xero(credentials)
OAuth2Credentials.__init__()
接受tenant_id
作为关键字参数。 -
在长时间使用API时,您需要在令牌过期时交换令牌。如果可用刷新令牌,则可以使用它来生成新令牌
>>> if credentials.expired(): >>> credentials.refresh() >>> # Then store the new credentials or token somewhere for future use: >>> cred_state = credentials.state >>> # or >>> new_token = credentials.token **Important**: ``credentials.state`` changes after a token swap. Be sure to persist the new state.
示例:Django OAuth2应用
此示例显示了在具有对联系人交易读写访问权限的Django应用中进行授权、自动令牌刷新和API使用的流程。如果服务器重启时清除了使用的缓存,则令牌将丢失,并将需要再次进行验证。
from django.http import HttpResponseRedirect
from django.core.cache import caches
from xero import Xero
from xero.auth import OAuth2Credentials
from xero.constants import XeroScopes
def start_xero_auth_view(request):
# Get client_id, client_secret from config file or settings then
credentials = OAuth2Credentials(
client_id, client_secret, callback_uri=callback_uri,
scope=[XeroScopes.OFFLINE_ACCESS, XeroScopes.ACCOUNTING_CONTACTS,
XeroScopes.ACCOUNTING_TRANSACTIONS]
)
authorization_url = credentials.generate_url()
caches['mycache'].set('xero_creds', credentials.state)
return HttpResponseRedirect(authorization_url)
def process_callback_view(request):
cred_state = caches['mycache'].get('xero_creds')
credentials = OAuth2Credentials(**cred_state)
auth_secret = request.build_absolute_uri()
credentials.verify(auth_secret)
credentials.set_default_tenant()
caches['mycache'].set('xero_creds', credentials.state)
def some_view_which_calls_xero(request):
cred_state = caches['mycache'].get('xero_creds')
credentials = OAuth2Credentials(**cred_state)
if credentials.expired():
credentials.refresh()
caches['mycache'].set('xero_creds', credentials.state)
xero = Xero(credentials)
contacts = xero.contacts.all()
...
使用PKCE凭据
PKCE是OAuth2中提供身份验证的另一种流程。它与标准OAuth2机制大致相同,但与正常流程不同,它旨在与无法安全存储私钥的应用程序一起使用,例如桌面、移动或单页应用程序,其中此类秘密可能被提取。仍然需要客户端ID。
如其他地方一样,OAuth2令牌的有效期为30分钟,但如果请求了 offline_access
作用域,则只能交换为新令牌。
有关PKCE流程的Xero文档可在 此处 找到。创建和验证凭据的步骤如下 (末尾附有CLI示例)
-
在Xero中注册您的应用,使用由您的应用提供的重定向URI以完成授权,例如
http://localhost:<port>/callback/
。您可以选择任何端口,并将其传递给在构造时凭据对象,同时使用提供的Client Id。 -
使用第一步中的详细信息构建一个
OAuth2Credentials
实例。>>> from xero.auth import OAuth2Credentials >>> >>> credentials = OAuth2PKCECredentials(client_id, port=my_port)
如有必要,请传入一个作用域列表以定义您的应用所需的作用域。例如,如果需要写入访问交易和薪酬员工
>>> from xero.constants import XeroScopes >>> >>> my_scope = [XeroScopes.ACCOUNTING_TRANSACTIONS, >>> XeroScopes.PAYROLL_EMPLOYEES] >>> credentials = OAuth2Credentials(client_id, scope=my_scope >>> port=my_port)
默认作用域为
['offline_access', 'accounting.transactions.read', 'accounting.contacts.read']
。offline_access
是必需的,以便令牌可刷新。有关作用域的更多详细信息,请参阅 Xero的oAuth2作用域文档。 -
调用
credentials.logon()
。这将打开一个浏览器窗口,并访问Xero认证页面。>>> credentials.logon()
认证器还将启动一个本地web服务器在提供的端口上。此web服务器将用于收集Xero返回的令牌。
默认的
PCKEAuthReceiver
类没有定义响应页面,因此浏览器将显示错误,对于所有交易都显示空页面。但是应用程序现在是授权的,将继续运行。如果您愿意,可以重写send_access_ok()
方法和send_error_page()
方法以创建更友好的用户体验。在任何情况下,一旦访问了回调URL,本地服务器将关闭。
-
现在您可以继续按照正常的OAuth2流程操作。现在可以使用凭据授权Xero会话。由于OAuth2允许对多个Xero组织进行身份验证,因此有必要设置xero客户端查询将运行的tenant_id。
>>> from xero import Xero >>> # Use the first xero organisation (tenant) permitted >>> credentials.set_default_tenant() >>> xero = Xero(credentials) >>> xero.contacts.all() >>> ...
如果步骤2中提供的作用域不需要访问组织(例如,仅请求单点登录的作用域),则无法使用Xero API进行请求,并将引发
set_default_tenant()
异常。要选择多个可能的Xero组织,可以显式设置
tenant_id
>>> tenants = credentials.get_tenants() >>> credentials.tenant_id = tenants[1]['tenantId'] >>> xero = Xero(credentials)
OAuth2Credentials.__init__()
接受tenant_id
作为关键字参数。 -
在长时间使用API时,您需要在令牌过期时交换令牌。如果可用刷新令牌,则可以使用它来生成新令牌
>>> if credentials.expired(): >>> credentials.refresh() >>> # Then store the new credentials or token somewhere for future use: >>> cred_state = credentials.state >>> # or >>> new_token = credentials.token **Important**: ``credentials.state`` changes after a token swap. Be sure to persist the new state.
CLI OAuth2应用程序示例
此示例展示了在具有对联系人和交易读写访问权限的Django应用程序中授权、自动令牌刷新和API使用。
每次此应用程序启动时都会请求身份验证,但您可以考虑使用用户keyring
来存储令牌。
from xero import Xero
from xero.auth import OAuth2PKCECredentials
from xero.constants import XeroScopes
# Get client_id, client_secret from config file or settings then
credentials = OAuth2PKCECredentials(
client_id, port=8080,
scope=[XeroScopes.OFFLINE_ACCESS, XeroScopes.ACCOUNTING_CONTACTS,
XeroScopes.ACCOUNTING_TRANSACTIONS]
)
credentials.logon()
credentials.set_default_tenant()
for contacts in xero.contacts.all()
print contact["Name"]
旧的认证方法
过去,Xero有“公共”、“私有”和“合作伙伴”应用程序的概念,每种应用程序都有自己的认证程序。然而,它们于2021年3月31日移除了公共应用程序的访问权限;私有应用程序于2021年9月30日被移除。合作伙伴应用程序仍然存在,但唯一支持的认证方法是OAuth2;这些现在被称为“OAuth2应用程序”。由于Xero不再支持这些旧的认证方法,因此PyXero也不支持。
使用Xero API
此API仍在开发中。目前,没有包装层来帮助创建真实对象,它只是返回由Xero API提供的确切格式的字典。这将在1.0版本之前改变成更有用的API。
Xero API对象提供了一个简单的API来检索和更新对象。例如,处理联系人:
# Retrieve all contact objects
>>> xero.contacts.all()
[{...contact info...}, {...contact info...}, {...contact info...}, ...]
# Retrieve a specific contact object
>>> xero.contacts.get(u'b2b5333a-2546-4975-891f-d71a8a640d23')
{...contact info...}
# Retrieve all contacts updated since 1 Jan 2013
>>> xero.contacts.filter(since=datetime(2013, 1, 1))
[{...contact info...}, {...contact info...}, {...contact info...}]
# Retrieve all contacts whose name is 'John Smith'
>>> xero.contacts.filter(Name='John Smith')
[{...contact info...}, {...contact info...}, {...contact info...}]
# Retrieve all contacts whose name starts with 'John'
>>> xero.contacts.filter(Name__startswith='John')
[{...contact info...}, {...contact info...}, {...contact info...}]
# Retrieve all contacts whose name ends with 'Smith'
>>> xero.contacts.filter(Name__endswith='Smith')
[{...contact info...}, {...contact info...}, {...contact info...}]
# Retrieve all contacts whose name starts with 'John' and ends with 'Smith'
>>> xero.contacts.filter(Name__startswith='John', Name__endswith='Smith')
[{...contact info...}, {...contact info...}, {...contact info...}]
# Retrieve all contacts whose name contains 'mit'
>>> xero.contacts.filter(Name__contains='mit')
[{...contact info...}, {...contact info...}, {...contact info...}]
# Create a new object
>>> xero.contacts.put({...contact info...})
# Create multiple new objects
>>> xero.contacts.put([{...contact info...}, {...contact info...}, {...contact info...}])
# Save an update to an existing object
>>> c = xero.contacts.get(u'b2b5333a-2546-4975-891f-d71a8a640d23')
>>> c['Name'] = 'John Smith'
>>> xero.contacts.save(c)
# Save multiple objects
>>> xero.contacts.save([c1, c2])
可以以Django的方式构建复杂的过滤器,例如检索联系人的发票。
>>> xero.invoices.filter(Contact_ContactID='83ad77d8-48a7-4f77-9146-e6933b7fb63b')
不支持此API的过滤器也可以使用类似下面的“raw”模式构建:
>>> xero.invoices.filter(raw='AmountDue > 0')
在处理大量数据时请小心,Xero API的响应时间将越来越长,或者会返回错误。如果查询可能会返回超过100个结果,则应使用page
参数:
# Grab 100 invoices created after 01-01-2013
>>> xero.invoices.filter(since=datetime(2013, 1, 1), page=1)
您还可以对返回的结果进行排序:
# Grab contacts ordered by EmailAddress
>>> xero.contacts.filter(order='EmailAddress DESC')
对于发票(以及其他可以检索为PDF的对象),通过设置Accept头访问PDF:
# Fetch a PDF
invoice = xero.invoices.get('af722e93-b64f-482d-9955-1b027bfec896', \
headers={'Accept': 'application/pdf'})
# Stream the PDF to the user (Django specific example)
response = HttpResponse(invoice, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="invoice.pdf"'
return response
使用相关对象的Xero GUID支持下载和上传附件:
# List attachments on a contact
>>> xero.contacts.get_attachments(c['ContactID'])
[{...attachment info...}, {...attachment info...}]
# Attach a PDF to a contact
>>> f = open('form.pdf', 'rb')
>>> xero.contacts.put_attachment(c['ContactID'], 'form.pdf', f, 'application/pdf')
>>> f.close()
>>> xero.contacts.put_attachment_data(c['ContactID'], 'form.pdf', data, 'application/pdf')
# Download an attachment
>>> f = open('form.pdf', 'wb')
>>> xero.contacts.get_attachment(c['ContactID'], 'form.pdf', f)
>>> f.close()
>>> data = xero.contacts.get_attachment_data(c['ContactID'], 'form.pdf')
此相同的API模式适用于以下API对象:
- 账户
- 附件
- 银行交易
- 银行转账
- 品牌主题
- 联系人组
- 联系人
- 贷项通知
- 货币
- 员工
- 费用索赔
- 发票
- 项目
- 日记账
- 手动日记账
- 组织
- 超额付款
- 付款
- 预付款
- 采购订单
- 收据
- 重复发票
- 报告
- 税率
- 跟踪类别
- 用户
薪酬
为了从Xero访问薪酬方法,您可以这样操作
xero.payrollAPI.payruns.all()
在薪酬API中,您可以访问:
- 员工
- 休假申请
- 薪酬项
- 薪酬日历
- 薪酬运行
- 工资单
- 超级基金
- 时间表
项目
要访问Xero的项目方法,您可以这样做
xero.projectsAPI.projects.all()
在项目API中,您可以访问:
- 项目
- 项目用户
- 任务
- 时间
内部机制
使用Xero API的包装器是一个非常棒的功能,但了解底层的确切操作也非常有趣。
过滤器操作符
filter
操作符包装了Xero API中的“where”关键字。
# Retrieves all contacts whose name is "John"
>>> xero.contacts.filter(name="John")
# Triggers this GET request:
Html encoded: <XERO_API_URL>/Contacts?where=name%3D%3D%22John%22
Non encoded: <XERO_API_URL>/Contacts?where=name=="John"
几个参数用编码的'&&'字符分隔。
# Retrieves all contacts whose first name is "John" and last name is "Doe"
>>> xero.contacts.filter(firstname="John", lastname="Doe")
# Triggers this GET request:
Html encoded: <XERO_API_URL>/Contacts?where=lastname%3D%3D%22Doe%22%26%26firstname%3D%3D%22John%22
Non encoded: <XERO_API_URL>/Contacts?where=lastname=="Doe"&&firstname=="John"
下划线会自动转换为“点”
# Retrieves all contacts whose name is "John"
>>> xero.contacts.filter(first_name="John")
# Triggers this GET request:
Html encoded: <XERO_API_URL>/Contacts?where=first.name%3D%3D%22John%22%
Non encoded: <XERO_API_URL>/Contacts?where=first.name=="John"
贡献
如果您要运行PyXero测试套件,除了PyXero的依赖项外,您还需要将以下依赖项添加到您的环境中
mock >= 1.0
Mock不包括在正式依赖项中,因为这些不是PyXero正常操作所必需的。它仅用于测试目的。
安装这些依赖项后,您可以通过在项目的根目录中运行以下命令来运行测试套件
$ python setup.py test
如果您发现PyXero有任何问题,您可以在GitHub问题上记录。在报告问题时,如果您能提供重现问题的步骤——可用于重现问题的调用序列和/或测试数据,那就非常有帮助了。
可以通过拉取请求提交新功能或错误修复。如果您希望您的拉取请求能够快速合并,请确保您包括为要添加/修复的行为编写的回归测试,或者提供为什么无法进行回归测试的良好解释。
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。