Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Python and dependency versions. #355

Merged
merged 6 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
name: CI

on: [pull_request]

# Cancel active CI runs for a PR before starting another run
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash # https://github.com/beeware/briefcase/pull/912

env:
FORCE_COLOR: "1"

jobs:
pre-commit:
runs-on: ubuntu-latest
Expand All @@ -19,11 +30,11 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13-dev"]
include:
- experimental: false

- python-version: "3.12-dev"
- python-version: "3.13-dev"
experimental: true
steps:
- uses: actions/checkout@v4
Expand Down
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.6.0
hooks:
- id: check-toml
- id: check-yaml
Expand All @@ -9,21 +9,21 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.17.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/psf/black
rev: 23.1.0
args: [--py39-plus]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.8.0
hooks:
- id: black
language_version: python3
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 7.1.1
hooks:
- id: flake8
69 changes: 68 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
[build-system]
requires = ["setuptools>=60", "setuptools_scm[toml]>=7.0", "wheel"]
requires = ["setuptools>=71.1.0", "setuptools_scm>=8.1.0"]
build-backend = "setuptools.build_meta"

[project]
dynamic = ["version"]
name = "pyxero"
description = "Python API for accessing the REST API of the Xero accounting tool."
readme = "README.md"
requires-python = ">= 3.9"
license.file = "LICENSE"
authors = [
{name="Russell Keith-Magee", email = "[email protected]"},
]
maintainers = [
{name="Russell Keith-Magee", email = "[email protected]"},
]
keywords = [
"xero",
"api",
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Financial and Insurance Industry",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business :: Financial :: Accounting",
]
dependencies = [
"requests >= 1.1.0, < 3",
"requests-oauthlib >= 0.3.0, < 3",
"python-dateutil>=2.8, < 3",
"PyJWT >= 1.6.4, < 3", # This is required as part of oauthlib but doesn't seem to get included sometimes.
"cryptography>=1.3.1", # As above, but fixes issue with missing module imports not picked up for some reason.
]

[project.optional-dependencies]
# Extras used by developers *of* briefcase are pinned to specific versions to
# ensure environment consistency.
dev = [
"pre-commit == 3.8.0",
"pytest == 8.3.2",
"tox == 4.18.0",
]

[project.urls]
Homepage = "https://github.com/freakboy3742/pyxero"
Tracker = "https://github.com/freakboy3742/pyxero/issues"
Source = "https://github.com/freakboy3742/pyxero"

[tool.setuptools.dynamic]
version = {attr="xero.__version__"}

[tool.pytest.ini_options]
testpaths = "tests"

[tool.isort]
profile = "black"
skip_glob = [
"venv*",
"local",
]
multi_line_output = 3
77 changes: 0 additions & 77 deletions setup.cfg

This file was deleted.

16 changes: 13 additions & 3 deletions src/xero/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import datetime
import hashlib
import http.server
import requests
import secrets
import sys
import threading
import webbrowser
from functools import partial
from urllib.parse import parse_qs, urlencode, urlparse

import requests
from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER
from requests_oauthlib import OAuth1, OAuth2, OAuth2Session

Expand Down Expand Up @@ -612,14 +613,23 @@ def headers(self):
@property
def expires_at(self):
"""Return the expires_at value from the token as a UTC datetime."""
return datetime.datetime.utcfromtimestamp(self.token["expires_at"])
if sys.version_info < (3, 11):
return datetime.datetime.utcfromtimestamp(self.token["expires_at"])
else:
return datetime.datetime.fromtimestamp(
self.token["expires_at"], datetime.UTC
)

def expired(self, seconds=30, now=None):
"""Check if the token has expired yet.
:param seconds: the minimum number of seconds allowed before expiry.
"""
if now is None:
now = datetime.datetime.utcnow()
if sys.version_info < (3, 11):
now = datetime.datetime.utcnow()
else:
now = datetime.datetime.now(datetime.UTC)

# Allow a bit of time for clock differences and round trip times
# to prevent false negatives. If users want the precise expiry,
# they can use self.expires_at.
Expand Down
5 changes: 3 additions & 2 deletions src/xero/basemanager.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import io
import json
import requests
from datetime import date, datetime
from urllib.parse import parse_qs
from uuid import UUID
from xml.etree.ElementTree import Element, SubElement, tostring
from xml.parsers.expat import ExpatError

import requests

from .auth import OAuth2Credentials
from .exceptions import (
XeroBadRequest,
Expand Down Expand Up @@ -447,7 +448,7 @@ def get_filter_value(key, value, value_type=None):
return value.isoformat()
elif key.endswith("ID") or value_type == UUID:
return "%s" % (
value.hex if type(value) == UUID else UUID(value).hex
value.hex if type(value) is UUID else UUID(value).hex
)
else:
return value
Expand Down
3 changes: 2 additions & 1 deletion src/xero/filesmanager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import requests
from urllib.parse import parse_qs

import requests

from xero.auth import OAuth2Credentials

from .constants import XERO_FILES_URL
Expand Down
3 changes: 2 additions & 1 deletion src/xero/projectmanager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import requests
from urllib.parse import parse_qs

import requests

from .constants import XERO_PROJECTS_URL
from .exceptions import (
XeroBadRequest,
Expand Down
21 changes: 16 additions & 5 deletions src/xero/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import datetime
import re
import sys

import requests

DATE = re.compile(
Expand Down Expand Up @@ -89,11 +91,20 @@ def parse_date(string, force_datetime=False):
}

if "timestamp" in values:
value = datetime.datetime.utcfromtimestamp(0) + datetime.timedelta(
hours=int(values.get("offset_h", 0)),
minutes=int(values.get("offset_m", 0)),
seconds=int(values["timestamp"]) / 1000.0,
)
if sys.version_info < (3, 11):
value = datetime.datetime.utcfromtimestamp(0) + datetime.timedelta(
hours=int(values.get("offset_h", 0)),
minutes=int(values.get("offset_m", 0)),
seconds=int(values["timestamp"]) / 1000.0,
)
else:
value = datetime.datetime.fromtimestamp(
0, datetime.UTC
) + datetime.timedelta(
hours=int(values.get("offset_h", 0)),
minutes=int(values.get("offset_m", 0)),
seconds=int(values["timestamp"]) / 1000.0,
)
return value

# I've made an assumption here, that a DateTime value will not
Expand Down
10 changes: 6 additions & 4 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json
import requests
import time
import unittest
from datetime import datetime, timedelta
from unittest.mock import Mock, patch
from urllib.parse import parse_qs, urlparse

import requests

from xero.api import Xero
from xero.auth import (
OAuth2Credentials,
Expand Down Expand Up @@ -747,9 +748,10 @@ def test_logon_opens_a_webbrowser(
port=8123,
)
server = Mock()
with patch("http.server.HTTPServer", return_value=server) as hs, patch(
"webbrowser.open"
) as wb:
with (
patch("http.server.HTTPServer", return_value=server) as hs,
patch("webbrowser.open") as wb,
):
credentials.logon()

self.assertTrue(wb.called)
Expand Down
Loading
Loading