跳转到主要内容

用于访问Xero会计工具REST API的Python API。

项目描述

PyXero

Python Versions PyPI Version Maturity BSD License Build Status

PyXero是访问由Xero会计工具提供的REST API的Python API。它允许访问公共、私有和合作伙伴应用程序。

快速入门

使用python包管理器安装此库

pip install pyxero

使用OAuth2凭证

OAuth2是一个开放标准的授权协议,允许用户为想要使用其帐户的应用程序提供特定权限。OAuth2身份验证是通过使用API获得的令牌来执行的;然后这些令牌将随后续请求一起提供。

OAuth2令牌的有效期为30分钟,但可以在任何时候交换为新令牌。Xero关于OAuth2流程的文档可以在这里找到。创建和验证凭证的流程如下 (最后有Django示例)

  1. 使用将由您的应用程序提供的重定向URI注册您的应用程序与Xero,例如 https://mysite.com/oauth/xero/callback/。请参阅第3步了解您的应用程序应该做什么。生成客户端密钥,然后将其存储在应用程序可以访问的地方,例如配置文件中。

  2. 使用第一步中的详细信息构建一个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的 文档

  3. 生成一个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。

  4. 授权后,用户将从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)
    
  5. 现在可以使用凭据授权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 作为关键字参数。

  6. 在长时间使用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示例)

  1. 在Xero中注册您的应用,使用由您的应用提供的重定向URI以完成授权,例如 http://localhost:<port>/callback/。您可以选择任何端口,并将其传递给在构造时凭据对象,同时使用提供的Client Id。

  2. 使用第一步中的详细信息构建一个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作用域文档

  3. 调用 credentials.logon()。这将打开一个浏览器窗口,并访问Xero认证页面。

    >>> credentials.logon()
    

    认证器还将启动一个本地web服务器在提供的端口上。此web服务器将用于收集Xero返回的令牌。

    默认的PCKEAuthReceiver类没有定义响应页面,因此浏览器将显示错误,对于所有交易都显示空页面。但是应用程序现在是授权的,将继续运行。如果您愿意,可以重写send_access_ok()方法和send_error_page()方法以创建更友好的用户体验。

    在任何情况下,一旦访问了回调URL,本地服务器将关闭。

  4. 现在您可以继续按照正常的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 作为关键字参数。

  5. 在长时间使用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问题上记录。在报告问题时,如果您能提供重现问题的步骤——可用于重现问题的调用序列和/或测试数据,那就非常有帮助了。

可以通过拉取请求提交新功能或错误修复。如果您希望您的拉取请求能够快速合并,请确保您包括为要添加/修复的行为编写的回归测试,或者提供为什么无法进行回归测试的良好解释。

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分布

pyxero-0.9.4.tar.gz (54.5 kB 查看哈希值)

上传时间

构建分布

pyxero-0.9.4-py3-none-any.whl (30.5 kB 查看哈希值)

上传时间 Python 3

支持者