From 301fe092f018e5e3316084ca8a132c30b6b049f4 Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:04:38 +1100 Subject: [PATCH 1/6] Add typing --- .gitignore | 10 +- README.md | 65 ++++--- payway/client.py | 193 ++++++++++--------- payway/conf.py | 2 + payway/constants.py | 5 +- payway/customers.py | 47 +++-- payway/exceptions.py | 31 ++- payway/model.py | 255 +++++++++++++------------ payway/test_utils.py | 10 + payway/transactions.py | 6 +- payway/utils.py | 10 +- pyproject.toml | 128 +++++++++++++ setup.py | 28 --- tests/data/card_transaction.json | 37 ++++ tests/data/customer.json | 20 ++ tests/data/customers.json | 16 ++ tests/data/refund_transaction.json | 34 ++++ tests/data/transaction.json | 37 ++++ tests/data/transactions.json | 20 ++ tests/data/void_transaction.json | 34 ++++ tests/test_client.py | 293 ++++------------------------- tests/test_customers.py | 41 ++-- tests/test_transactions.py | 29 +-- uv.lock | 151 +++++++++++++++ 24 files changed, 874 insertions(+), 628 deletions(-) create mode 100644 payway/test_utils.py create mode 100644 pyproject.toml delete mode 100644 setup.py create mode 100644 tests/data/card_transaction.json create mode 100644 tests/data/customer.json create mode 100644 tests/data/customers.json create mode 100644 tests/data/refund_transaction.json create mode 100644 tests/data/transaction.json create mode 100644 tests/data/transactions.json create mode 100644 tests/data/void_transaction.json create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 05d9695..d554bcb 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,12 @@ __pycache__/ dist/ build/ *.egg-info -.envrc \ No newline at end of file +.envrc +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.ruff_cache \ No newline at end of file diff --git a/README.md b/README.md index ffe731a..c1aa9e4 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ - Void transactions - Update a customer's payment setup in PayWay -# Install +## Install -``` +```bash pip install python-payway ``` -# Take payment using a stored credit card +## Take payment using a stored credit card Create a Client class with your PayWay API credentials @@ -41,7 +41,7 @@ customer = PayWayCustomer(custom_id='c981a', state='NSW', postal_code='2000') ``` - + Create a PayWayCard class with your customer's card details ```python @@ -62,7 +62,7 @@ payway_customer, customer_errors = client.create_customer(customer) ``` Note the 'payway_customer' object contains the full customer response fields from PayWay. - + Create a Payment class with the payment details and process the transaction ```python @@ -74,8 +74,8 @@ payment = PayWayPayment(customer_number=customer_number, order_number='', ip_address='') transaction, errors = client.process_payment(payment) -``` - +``` + Check the `transaction` for the result ```python @@ -83,7 +83,7 @@ if not errors and transaction.status == 'approved': # process successful response ``` -# Take payment using a credit card token only +## Take payment using a credit card token only ```python client = Client(merchant_id='', @@ -109,10 +109,10 @@ payment = PayWayPayment(customer_number=customer_number, transaction, errors = client.process_payment(payment) ``` -# Handling errors +## Handling errors Documented errors (such as 422 Unprocessable entity) are parsed into an PaymentError class that you can use in an customer error message. -https://www.payway.com.au/docs/rest.html#http-response-codes +For more info, visit ```python if errors: @@ -122,9 +122,10 @@ if errors: print(error.field_name) # or use a method PaymentError().list_to_message(errors) -``` +``` + +## Direct Debit -# Direct Debit Direct debit transactions are possible by creating a token from a bank account: ```python @@ -137,15 +138,15 @@ Store the token with a customer in PayWay using the same process as the card out Note: direct debit transactions take days to process so they must be polled regularly for the latest transaction status from the customer's bank. -# Lookup transaction +## Lookup transaction Poll a transaction using the `get_transaction` method. ```python transaction, errors = client.get_transaction(transaction.transaction_id) -``` +``` -# Process and capture a pre-authorisation +## Process and capture a pre-authorisation To process a credit card pre-authorisation using a credit card stored against a customer use `preAuth` as the `transaction_type` along with the customer's PayWay number, amount and currency. @@ -170,7 +171,7 @@ capture_payment = PayWayPayment(transaction_type='capture', transaction, errors = client.process_payment(capture_payment) ``` -# Refunds +## Refunds Refund a transaction by supplying a PayWay transaction ID and the refund amount. @@ -181,7 +182,7 @@ refund, errors = client.refund_transaction( ) ``` -# Voiding a transaction +## Voiding a transaction Void a transaction by supplying a PayWay transaction ID. @@ -189,7 +190,7 @@ Void a transaction by supplying a PayWay transaction ID. void_transaction, errors = client.void_transaction(transaction.transaction_id) ``` -# Update Payment Setup +## Update Payment Setup Update a customer's payment setup with a new credit card or bank account in PayWay. Supply the new token and an existing PayWay customer number. @@ -197,18 +198,28 @@ Update a customer's payment setup with a new credit card or bank account in PayW payment_setup, errors = client.update_payment_setup(new_token, payway_customer.customer_number) ``` -# Additional notes -PayWay API documentation -https://www.payway.com.au/docs/rest.html +## Additional notes + +PayWay API documentation -It is recommended to use PayWay's Trusted Frame https://www.payway.com.au/docs/rest.html#trusted-frame +It is recommended to use PayWay's Trusted Frame when creating a single use token of a card or bank account so your PCI-compliance scope is reduced. -# Fraud +## Fraud + +Please follow PayWay's advice about reducing your risk of fraudulent transactions. -Please follow PayWay's advice about reducing your risk of fraudulent transactions. -https://www.payway.com.au/docs/card-testing.html#card-testing +## Running the project -# Testing +```bash +uv python install 3.8.19 +uv venv +source .venv/bin/activate +uv sync +``` + +## Testing -1. Run the tests using `python -m unittest discover tests` +```bash +python -m unittest discover tests +``` diff --git a/payway/client.py b/payway/client.py index 57b0061..0fab6e8 100644 --- a/payway/client.py +++ b/payway/client.py @@ -1,21 +1,28 @@ +from __future__ import annotations + import json from logging import getLogger +from typing import NoReturn + import requests -from payway.conf import TOKEN_NO_REDIRECT, CUSTOMER_URL, TRANSACTION_URL +from payway.conf import CUSTOMER_URL, TOKEN_NO_REDIRECT, TRANSACTION_URL from payway.constants import ( - CREDIT_CARD_PAYMENT_CHOICE, BANK_ACCOUNT_PAYMENT_CHOICE, + CREDIT_CARD_PAYMENT_CHOICE, VALID_PAYMENT_METHOD_CHOICES, ) from payway.customers import CustomerRequest from payway.exceptions import PaywayError from payway.model import ( + BankAccount, + PaymentError, + PaymentSetup, + PayWayCard, PayWayCustomer, + PayWayPayment, PayWayTransaction, - PaymentError, ServerError, - PaymentSetup, TokenResponse, ) from payway.transactions import TransactionRequest @@ -34,8 +41,12 @@ class Client(CustomerRequest, TransactionRequest): publishable_api_key = "" def __init__( - self, merchant_id, bank_account_id, secret_api_key, publishable_api_key - ): + self, + merchant_id: str, + bank_account_id: str, + secret_api_key: str, + publishable_api_key: str, + ) -> NoReturn: """ :param merchant_id : str = PayWay Merchant ID :param bank_account_id : str = PayWay Bank Account ID @@ -43,7 +54,10 @@ def __init__( :param publishable_api_key : str = PayWay Publishable API Key """ self._validate_credentials( - merchant_id, bank_account_id, secret_api_key, publishable_api_key + merchant_id, + bank_account_id, + secret_api_key, + publishable_api_key, ) self.merchant_id = merchant_id self.bank_account_id = bank_account_id @@ -60,31 +74,33 @@ def __init__( self.session_no_headers = session_no_headers def _validate_credentials( - self, merchant_id, bank_account_id, secret_api_key, publishable_api_key - ): - if ( - not merchant_id - or not bank_account_id - or not secret_api_key - or not publishable_api_key - ): + self, + merchant_id: str, + bank_account_id: str, + secret_api_key: str, + publishable_api_key: str, + ) -> NoReturn: + if not merchant_id or not bank_account_id or not secret_api_key or not publishable_api_key: if not secret_api_key or not publishable_api_key: logger.error("PayWay API keys not found") raise PaywayError( - message="PayWay API keys not found", code="INVALID_API_KEYS" + message="PayWay API keys not found", + code="INVALID_API_KEYS", ) logger.error( - "Merchant ID, bank account ID, secret API key, publishable API key are " - "invalid" + "Merchant ID, bank account ID, secret API key, publishable API key are " "invalid", ) raise PaywayError( - message="Invalid credentials", code="INVALID_API_CREDENTIALS" + message="Invalid credentials", + code="INVALID_API_CREDENTIALS", ) - def get_request(self, endpoint): - return requests.get(url=endpoint, auth=(self.secret_api_key, "")) + def get_request(self, endpoint: str) -> requests.Response: + return requests.get(url=endpoint, auth=(self.secret_api_key, ""), timeout=30) - def post_request(self, endpoint, data, auth=None, idempotency_key=None): + def post_request( + self, endpoint: str, data: dict, auth: tuple | None = None, idempotency_key: str | None = None + ) -> requests.Response: """ Supply an idempotency_key to avoid duplicate POSTs https://www.payway.com.au/docs/rest.html#avoiding-duplicate-posts @@ -94,15 +110,20 @@ def post_request(self, endpoint, data, auth=None, idempotency_key=None): headers = {"content-type": "application/x-www-form-urlencoded"} if idempotency_key: headers["Idempotency-Key"] = idempotency_key - return requests.post(url=endpoint, auth=auth, data=data, headers=headers) + return requests.post(url=endpoint, auth=auth, data=data, headers=headers, timeout=30) - def put_request(self, endpoint, data): - headers = {"content-type": "application/x-www-form-urlencoded"} + def put_request(self, endpoint: str, data: dict) -> requests.Response: return requests.put( - url=endpoint, auth=(self.secret_api_key, ""), data=data, headers=headers + url=endpoint, + auth=(self.secret_api_key, ""), + data=data, + headers={"content-type": "application/x-www-form-urlencoded"}, + timeout=30, ) - def create_token(self, payway_obj, payment_method, idempotency_key=None): + def create_token( + self, payway_obj: BankAccount | PayWayCard, payment_method: str, idempotency_key: str | None = None + ) -> tuple[TokenResponse, list]: """ Creates a single use token for a Customer's payment setup (credit card or bank account) :param payway_obj: object: one of model.PayWayCard or model.BankAccount object @@ -115,15 +136,15 @@ def create_token(self, payway_obj, payment_method, idempotency_key=None): elif payment_method == "direct_debit": payway_payment_method = BANK_ACCOUNT_PAYMENT_CHOICE else: + valid_payment_method_choices = ", ".join(VALID_PAYMENT_METHOD_CHOICES) raise PaywayError( - message="Invalid payment method. Must be one of %s" - % ", ".join(VALID_PAYMENT_METHOD_CHOICES), + message=f"Invalid payment method. Must be one of {valid_payment_method_choices}", code="INVALID_PAYMENT_METHOD", ) data.update( { "paymentMethod": payway_payment_method, - } + }, ) endpoint = TOKEN_NO_REDIRECT logger.info("Sending Create Token request to PayWay.") @@ -133,33 +154,32 @@ def create_token(self, payway_obj, payment_method, idempotency_key=None): auth=(self.publishable_api_key, ""), idempotency_key=idempotency_key, ) - logger.info("Response from server: %s" % response) errors = self._validate_response(response) if errors: return None, errors - else: - token_response = TokenResponse().from_dict(response.json()) - return token_response, errors + token_response = TokenResponse().from_dict(response.json()) + return token_response, errors - def create_card_token(self, card, idempotency_key=None): + def create_card_token(self, card: PayWayCard, idempotency_key: str | None = None) -> tuple[TokenResponse, list]: """ :param card: PayWayCard object represents a customer's credit card details :param idempotency_key: str: unique value to avoid duplicate POSTs - See model.PayWayCard """ return self.create_token(card, "card", idempotency_key=idempotency_key) - def create_bank_account_token(self, bank_account, idempotency_key=None): + def create_bank_account_token(self, bank_account: BankAccount, idempotency_key: str | None = None) -> str: """ :param bank_account: BankAccount object represents a customer's bank account :param idempotency_key: str: unique value to avoid duplicate POSTs See model.BankAccount """ return self.create_token( - bank_account, "direct_debit", idempotency_key=idempotency_key + bank_account, + "direct_debit", + idempotency_key=idempotency_key, ) - def create_customer(self, customer, idempotency_key=None): + def create_customer(self, customer: PayWayCustomer, idempotency_key: str | None = None) -> tuple[PayWayCustomer | None, list]: """ Create a customer in PayWay system @@ -174,29 +194,30 @@ def create_customer(self, customer, idempotency_key=None): data = customer.to_dict() data.update( - {"merchantId": self.merchant_id, "bankAccountId": self.bank_account_id} + {"merchantId": self.merchant_id, "bankAccountId": self.bank_account_id}, ) logger.info("Sending Create Customer request to PayWay.") if customer.custom_id: - endpoint = "{}/{}".format(CUSTOMER_URL, customer.custom_id) + endpoint = f"{CUSTOMER_URL}/{customer.custom_id}" response = self.put_request(endpoint, data) else: - endpoint = "{}".format(CUSTOMER_URL) + endpoint = f"{CUSTOMER_URL}" response = self.post_request( - endpoint, data, idempotency_key=idempotency_key + endpoint, + data, + idempotency_key=idempotency_key, ) - logger.info("Response from server: %s" % response) errors = self._validate_response(response) if errors: return None, errors - else: - customer = PayWayCustomer().from_dict(response.json()) - return customer, errors - def process_payment(self, payment, idempotency_key=None): + customer = PayWayCustomer().from_dict(response.json()) + return customer, errors + + def process_payment(self, payment: PayWayPayment, idempotency_key: str | None = None) -> tuple[PayWayTransaction, list]: """ Process an individual payment against a Customer with active Recurring Billing setup. :param payment: PayWayPayment object (see model.PayWayPayment) @@ -206,16 +227,14 @@ def process_payment(self, payment, idempotency_key=None): endpoint = TRANSACTION_URL logger.info("Sending Process Payment request to PayWay.") response = self.post_request(endpoint, data, idempotency_key=idempotency_key) - logger.info("Response from server: %s" % response) errors = self._validate_response(response) if errors: return None, errors - else: - # convert response to PayWayTransaction object - transaction = PayWayTransaction.from_dict(response.json()) + # convert response to PayWayTransaction object + transaction = PayWayTransaction.from_dict(response.json()) return transaction, errors - def _validate_response(self, response): + def _validate_response(self, response: requests.Response) -> list | None: """ Validates all responses from PayWay to catch documented PayWay errors. :param response: requests response object @@ -234,73 +253,63 @@ def _validate_response(self, response): 501, 503, ]: - http_error_msg = "%s Client Error: %s for url: %s" % ( - response.status_code, - response.reason, - response.url, - ) + http_error_msg = f"{response.status_code} Client Error: {response.reason} for url: {response.url}" raise PaywayError(code=response.status_code, message=http_error_msg) - elif response.status_code in [404, 422]: # Documented PayWay errors in JSON + if response.status_code in [404, 422]: # Documented PayWay errors in JSON # parse error message - errors = response.json() - payway_errors = PaymentError().from_dict(errors) - # instead of raising an exception, return the specific PayWay errors as a list - return payway_errors + return PaymentError().from_dict(response.json()) - elif response.status_code == 500: + if response.status_code == 500: try: errors = response.json() except json.JSONDecodeError: raise PaywayError( - code=response.status_code, message="Internal server error" + code=response.status_code, + message="Internal server error", ) # Documented PayWay server errors in JSON payway_error = ServerError().from_dict(errors) message = payway_error.to_message() raise PaywayError(code=response.status_code, message=message) - else: - return None + return None - def get_transaction(self, transaction_id): + def get_transaction(self, transaction_id: int) -> tuple[PayWayTransaction, list]: """ Lookup and return a transaction if found in PayWay :param transaction_id: str A PayWay transaction ID """ - endpoint = "%s/%s" % (TRANSACTION_URL, str(transaction_id)) + endpoint = f"{TRANSACTION_URL}/{transaction_id}" response = self.get_request(endpoint) - logger.info("Response from server: %s" % response) errors = self._validate_response(response) if errors: return None, errors - else: - transaction = PayWayTransaction.from_dict(response.json()) + transaction = PayWayTransaction.from_dict(response.json()) return transaction, errors - def void_transaction(self, transaction_id, idempotency_key=None): + def void_transaction(self, transaction_id: int, idempotency_key: str | None = None) -> tuple[PayWayTransaction, list]: """ Void a transaction in PayWay :param transaction_id: str A PayWay transaction ID :param idempotency_key: str: unique value to avoid duplicate POSTs """ - endpoint = "%s/%s/void" % (TRANSACTION_URL, transaction_id) + endpoint = f"{TRANSACTION_URL}/{transaction_id}/void" response = self.post_request(endpoint, data={}, idempotency_key=idempotency_key) errors = self._validate_response(response) if errors: return None, errors - else: - transaction = PayWayTransaction.from_dict(response.json()) + transaction = PayWayTransaction.from_dict(response.json()) return transaction, errors def refund_transaction( self, - transaction_id, - amount, - order_id=None, - ip_address=None, - idempotency_key=None, - ): + transaction_id: int, + amount: float, + order_id: str | None = None, + ip_address: str | None = None, + idempotency_key: str | None = None, + ) -> tuple[PayWayTransaction, list]: """ Refund a transaction in PayWay :param transaction_id: str A PayWay transaction ID @@ -309,7 +318,6 @@ def refund_transaction( :param ip_address: str optional IP address :param idempotency_key: str: unique value to avoid duplicate POSTs """ - endpoint = TRANSACTION_URL data = { "transactionType": "refund", "parentTransactionId": transaction_id, @@ -319,35 +327,33 @@ def refund_transaction( data["orderNumber"] = order_id if ip_address: data["customerIpAddress"] = ip_address - response = self.post_request(endpoint, data, idempotency_key=idempotency_key) + response = self.post_request(TRANSACTION_URL, data, idempotency_key=idempotency_key) errors = self._validate_response(response) if errors: return None, errors - else: - transaction = PayWayTransaction.from_dict(response.json()) + transaction = PayWayTransaction.from_dict(response.json()) return transaction, errors - def get_customer(self, customer_id): + def get_customer(self, customer_id: str) -> tuple[PayWayCustomer, list]: """ Returns a PayWay Customer's Payment Setup, [Payment] Schedule, Contact Details, Custom Fields and Notes :param customer_id str PayWay customer ID in PayWay system """ - endpoint = "%s/%s" % (CUSTOMER_URL, str(customer_id)) + endpoint = f"{CUSTOMER_URL}/{customer_id}" response = self.get_request(endpoint) errors = self._validate_response(response) if errors: return None, errors - else: - customer = PayWayCustomer.from_dict(response.json()) + customer = PayWayCustomer.from_dict(response.json()) return customer, errors - def update_payment_setup(self, token, customer_id): + def update_payment_setup(self, token: str, customer_id: str) -> tuple[PaymentSetup, str]: """ Updates the Customer's Payment Setup with a new Credit Card or Bank Account. :param token: PayWay credit card or bank account token :param customer_id: PayWay customer ID """ - endpoint = "%s/%s/payment-setup" % (CUSTOMER_URL, str(customer_id)) + endpoint = f"{CUSTOMER_URL}/{customer_id}/payment-setup" data = { "singleUseTokenId": token, "merchantId": self.merchant_id, @@ -357,6 +363,5 @@ def update_payment_setup(self, token, customer_id): errors = self._validate_response(response) if errors: return None, errors - else: - ps = PaymentSetup.from_dict(response.json()) + ps = PaymentSetup.from_dict(response.json()) return ps, errors diff --git a/payway/conf.py b/payway/conf.py index 5e2174f..1475d4c 100644 --- a/payway/conf.py +++ b/payway/conf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + PAYWAY_API_URL = "https://api.payway.com.au/rest/v1" TOKEN_URL = PAYWAY_API_URL + "/single-use-tokens-redirect" TRANSACTION_URL = PAYWAY_API_URL + "/transactions" diff --git a/payway/constants.py b/payway/constants.py index 3f9503d..5393b29 100644 --- a/payway/constants.py +++ b/payway/constants.py @@ -1,5 +1,4 @@ -from __future__ import unicode_literals - +from __future__ import annotations TRANSACTION_APPROVED = "0" @@ -55,7 +54,7 @@ "QN": "Configuration Error", "QO": "Missing Payment Instrument", "QP": "Missing Supplier Account", - "QQ": "Invalid Credit Card \ Invalid Credit Card Verification Number", + "QQ": r"Invalid Credit Card \ Invalid Credit Card Verification Number", "QR": "Transaction Retry", "QS": "Transaction Successful", "QT": "Invalid currency", diff --git a/payway/customers.py b/payway/customers.py index 6170cf5..7e5e448 100644 --- a/payway/customers.py +++ b/payway/customers.py @@ -1,29 +1,32 @@ +from __future__ import annotations + import requests from payway.conf import CUSTOMER_URL +from payway.model import PayWayCustomer from payway.utils import json_list -class CustomerRequest(object): +class CustomerRequest: session = requests.Session() session_no_headers = requests.Session() @json_list("delete_customer") - def delete_customer(self, customer_number): + def delete_customer(self, customer_number: int) -> requests.Response: """ Returns a list of transactions """ - return self.session_no_headers.delete("%s/%s" % (CUSTOMER_URL, customer_number)) + return self.session_no_headers.delete(f"{CUSTOMER_URL}/{customer_number}") @json_list("schedule_payments") def schedule_payments( self, - customer_number, - frequency, - next_payment_date, - regular_amount, - next_amount=None, - ): + customer_number: int, + frequency: str, + next_payment_date: str, + regular_amount: int, + next_amount: int | None = None, + ) -> requests.Response: """ Schedule a new payment for a customer :param customer_number PayWay customer number @@ -39,40 +42,43 @@ def schedule_payments( "regularPrincipalAmount": regular_amount, } return self.session_no_headers.put( - "%s/%s/schedule" % (CUSTOMER_URL, customer_number), data=data + f"{CUSTOMER_URL}/{customer_number}/schedule", + data=data, ) @json_list("schedule_payments") - def stop_schedule(self, customer_number): + def stop_schedule(self, customer_number: int) -> requests.Response: """ Stop a schedule for a customer """ return self.session_no_headers.delete( - "%s/%s/schedule" % (CUSTOMER_URL, customer_number) + f"{CUSTOMER_URL}/{customer_number}/schedule", ) @json_list("stop_all_payments") - def stop_all_payments(self, customer_number): + def stop_all_payments(self, customer_number: int) -> requests.Response: """ Stops any new payments using the stored credit card or bank account/ """ data = {"stopped": "true"} return self.session_no_headers.patch( - "%s/%s/payment-setup" % (CUSTOMER_URL, customer_number), data=data + f"{CUSTOMER_URL}/{customer_number}/payment-setup", + data=data, ) @json_list("start_all_payments") - def start_all_payments(self, customer_number): + def start_all_payments(self, customer_number: int) -> requests.Response: """ Allows any new payments using the stored credit card or bank account. """ data = {"stopped": "false"} return self.session_no_headers.patch( - "%s/%s/payment-setup" % (CUSTOMER_URL, customer_number), data=data + f"{CUSTOMER_URL}/{customer_number}/payment-setup", + data=data, ) @json_list("update_contact_details") - def update_contact_details(self, customer_number, customer=None, **options): + def update_contact_details(self, customer_number: int, customer: PayWayCustomer | None = None, **options: dict) -> requests.Response: """ param: customer_number: PayWay customer number param: customer PayWayCustomer object @@ -83,14 +89,15 @@ def update_contact_details(self, customer_number, customer=None, **options): data.update(customer.to_dict()) data.update(options) return self.session.put( - "%s/%s/contact" % (CUSTOMER_URL, customer_number), data=data + f"{CUSTOMER_URL}/{customer_number}/contact", + data=data, ) @json_list("list_customers") - def list_customers(self): + def list_customers(self) -> requests.Response: """ List all customers in PayWay Returns paginated list of customerNumber, customerName """ # TODO: add page numbers - return self.session_no_headers.get("%s" % CUSTOMER_URL) + return self.session_no_headers.get(CUSTOMER_URL) diff --git a/payway/exceptions.py b/payway/exceptions.py index e3a1f41..440b2a2 100644 --- a/payway/exceptions.py +++ b/payway/exceptions.py @@ -1,29 +1,22 @@ +from __future__ import annotations + +from typing import NoReturn + + class PaywayError(Exception): - _code = None - _message = None + _code: str = None + _message: str = None - def __init__(self, code, message, *args, **kwargs): + def __init__(self, code: str, message: str, *args: dict, **kwargs: dict) -> NoReturn: """ code : str = PayWay API response/error code message : str = appropriate message """ - super(PaywayError, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._code = code - self._message = "{}: {}".format(code, message).encode("utf-8") - - def __bytes__(self): - return self._message - - def __unicode__(self): - try: - return unicode(self.__bytes__()) - except NameError: - return str(self.__bytes__(), "utf-8") - - def __str__(self): - return self.__bytes__().decode("utf-8") + self._message = f"{code}: {message}".encode() - def __repr__(self): - return self.__unicode__() + def __str__(self) -> str: + return self.message diff --git a/payway/model.py b/payway/model.py index 2b0b968..c619cc0 100644 --- a/payway/model.py +++ b/payway/model.py @@ -1,16 +1,21 @@ -class BankAccount(object): +from __future__ import annotations + +from typing import Any + + +class BankAccount: """ account_name: str: Name used to open bank account. bsb: str: bank account BSB account_number: str: bank account number """ - def __init__(self, account_name, bsb, account_number): + def __init__(self, account_name: str, bsb: str, account_number: str) -> None: self.account_name = account_name self.bsb = bsb self.account_number = account_number - def to_dict(self): + def to_dict(self) -> dict[str, Any]: return { "accountName": self.account_name, "bsb": self.bsb, @@ -18,22 +23,22 @@ def to_dict(self): } -class PayWayCard(object): +class PayWayCard: def __init__( self, - card_number=None, - cvn=None, - card_holder_name=None, - expiry_date_month=None, - expiry_date_year=None, - ): + card_number: str | None = None, + cvn: str | None = None, + card_holder_name: str | None = None, + expiry_date_month: str | None = None, + expiry_date_year: str | None = None, + ) -> None: self.card_number = card_number self.cvn = cvn self.card_holder_name = card_holder_name self.expiry_date_month = expiry_date_month self.expiry_date_year = expiry_date_year - def to_dict(self): + def to_dict(self) -> dict[str, Any]: return { "cardNumber": self.card_number, "cvn": self.cvn, @@ -43,7 +48,7 @@ def to_dict(self): } @staticmethod - def from_dict(payway_card): + def from_dict(payway_card: dict) -> PayWayCard: card = PayWayCard() if payway_card.get("maskedCardNumber"): card.card_number = payway_card.get("maskedCardNumber") @@ -56,28 +61,28 @@ def from_dict(payway_card): return card -class PayWayCustomer(object): +class PayWayCustomer: def __init__( self, - custom_id=None, - customer_name=None, - email_address=None, - send_email_receipts=None, - phone_number=None, - street=None, - street2=None, - city_name=None, - state=None, - postal_code=None, - token=None, - customer_number=None, - payment_setup=None, - notes=None, - custom_field_1=None, - custom_field_2=None, - custom_field_3=None, - custom_field_4=None, - ): + custom_id: str | None = None, + customer_name: str | None = None, + email_address: str | None = None, + send_email_receipts: bool | None = None, + phone_number: str | None = None, + street: str | None = None, + street2: str | None = None, + city_name: str | None = None, + state: str | None = None, + postal_code: str | None = None, + token: str | None = None, + customer_number: str | None = None, + payment_setup: PaymentSetup | None = None, + notes: str | None = None, + custom_field_1: str | None = None, + custom_field_2: str | None = None, + custom_field_3: str | None = None, + custom_field_4: str | None = None, + ) -> None: self.custom_id = custom_id self.customer_name = customer_name self.email_address = email_address @@ -97,7 +102,7 @@ def __init__( self.customField3 = custom_field_3 self.customField4 = custom_field_4 - def to_dict(self): + def to_dict(self) -> dict[str, Any]: customer = { "customerName": self.customer_name, "emailAddress": self.email_address, @@ -119,7 +124,7 @@ def to_dict(self): return customer @staticmethod - def from_dict(response): + def from_dict(response: dict) -> PayWayCustomer: """ Parse PayWay Customer response data :param response: dict PayWay response dictionary @@ -141,7 +146,7 @@ def from_dict(response): if response.get("paymentSetup") is not None: customer.payment_setup = PaymentSetup().from_dict( - response.get("paymentSetup") + response.get("paymentSetup"), ) if response.get("customFields") is not None: @@ -155,13 +160,13 @@ def from_dict(response): return customer -class PaymentError(object): - field_name = None - message = None - field_value = None +class PaymentError: + field_name: str = None + message: str = None + field_value: str = None @staticmethod - def from_dict(payway_response): + def from_dict(payway_response: dict) -> list: """ Returns a list of errors from PayWay :param: payway_response: dict PayWay response dictionary @@ -176,15 +181,11 @@ def from_dict(payway_response): payment_errors.append(payway_error) return payment_errors - def to_message(self): - return "Field: {} Message: {} Field Value: {}".format( - self.field_name, - self.message, - self.field_value, - ) + def to_message(self) -> str: + return f"Field: {self.field_name} Message: {self.message} Field Value: {self.field_value}" @staticmethod - def list_to_message(payway_errors): + def list_to_message(payway_errors: list[PaymentError]) -> str: """ Convert list to readable string :param payway_errors: @@ -198,12 +199,12 @@ def list_to_message(payway_errors): return message -class ServerError(object): - error_number = None - trace_code = None +class ServerError: + error_number: int = None + trace_code: str = None @staticmethod - def from_dict(response): + def from_dict(response: dict) -> ServerError: """ :param: response: dict PayWay response dictionary """ @@ -212,13 +213,11 @@ def from_dict(response): payway_error.trace_code = response.get("traceCode") return payway_error - def to_message(self): - return "Error number: {} Trace code: {}".format( - self.error_number, self.trace_code - ) + def to_message(self) -> str: + return f"Error number: {self.error_number} Trace code: {self.trace_code}" -class PayWayPayment(object): +class PayWayPayment: """ customer_number: Customer to which this payment belongs. transaction_type: payment, refund, preAuth, capture or accountVerification @@ -233,16 +232,16 @@ class PayWayPayment(object): def __init__( self, - transaction_type, - customer_number=None, - amount=None, - currency=None, - order_number=None, - ip_address=None, - parent_transaction_id=None, - token=None, - merchant_id=None, - ): + transaction_type: str, + customer_number: int | None = None, + amount: int | None = None, + currency: str | None = None, + order_number: str | None = None, + ip_address: str | None = None, + parent_transaction_id: str | None = None, + token: str | None = None, + merchant_id: str | None = None, + ) -> None: self.transaction_type = transaction_type self.customer_number = customer_number self.amount = amount @@ -253,7 +252,7 @@ def __init__( self.token = token self.merchant_id = merchant_id - def to_dict(self): + def to_dict(self) -> dict[str, Any]: payment = { "customerNumber": self.customer_number, "transactionType": self.transaction_type, # default to "payment" @@ -272,45 +271,45 @@ def to_dict(self): return payment -class PayWayTransaction(object): - transaction_id = None - receipt_number = None - status = None - response_code = None - response_text = None - transaction_type = None - customer_number = None - customer_name = None - customer_email = None - bpay_ref = None - order_number = None - currency = None - principal_amount = None - surcharge_amount = None - payment_amount = None - payment_method = None - declined_date = None - card = None - merchant = None - virtual_account = None - australia_post = None - bpay = None - your_bank_account = None - customer_paypal_account = None - your_paypal_account = None - transaction_date_time = None - user = None - settlement_date = None - parent_transaction = None - ip_address = None - fraud_result = None - ip_country = None - card_country = None - custom_fields = None - is_voidable = None - is_refundable = None - - def to_dict(self): +class PayWayTransaction: + transaction_id: int = None + receipt_number: str = None + status: str = None + response_code: str = None + response_text: str = None + transaction_type: str = None + customer_number: str = None + customer_name: str = None + customer_email: str = None + bpay_ref: str = None + order_number: str = None + currency: str = None + principal_amount: float = None + surcharge_amount: float = None + payment_amount: float = None + payment_method: str = None + declined_date: str = None + card: PayWayCard = None + merchant: Merchant = None + virtual_account: dict = None + australia_post: dict = None + bpay: dict = None + your_bank_account: dict = None + customer_paypal_account: dict = None + your_paypal_account: dict = None + transaction_date_time: str = None + user: dict = None + settlement_date: str = None + parent_transaction: dict = None + ip_address: str = None + fraud_result: str = None + ip_country: str = None + card_country: str = None + custom_fields: dict = None + is_voidable: bool = None + is_refundable: bool = None + + def to_dict(self) -> dict[str, Any]: return { "transactionId": self.transaction_id, "receiptNumber": self.receipt_number, @@ -349,7 +348,7 @@ def to_dict(self): } @staticmethod - def from_dict(response): + def from_dict(response: dict) -> PayWayTransaction: """ :param: response: dict PayWay response dictionary """ @@ -396,7 +395,7 @@ def from_dict(response): return transaction -class Merchant(object): +class Merchant: """ merchantId Issued by us to uniquely identify a merchant facility merchantName @@ -407,14 +406,14 @@ class Merchant(object): account """ - merchant_id = None - merchant_name = None - settlement_bsb = None - settlement_account_number = None - surcharge_bsb = None - surcharge_account_number = None + merchant_id: str = None + merchant_name: str = None + settlement_bsb: str = None + settlement_account_number: str = None + surcharge_bsb: str = None + surcharge_account_number: str = None - def to_dict(self): + def to_dict(self) -> dict[str, None]: return { "merchantId": self.merchant_id, "merchantName": self.merchant_name, @@ -425,7 +424,7 @@ def to_dict(self): } @staticmethod - def from_dict(payway_obj): + def from_dict(payway_obj: dict) -> Merchant: """ :param: payway_obj: dict PayWay response dictionary """ @@ -439,14 +438,14 @@ def from_dict(payway_obj): return merchant -class PaymentSetup(object): - payment_method = None - stopped = None - credit_card = None - merchant = None +class PaymentSetup: + payment_method: str = None + stopped: bool = None + credit_card: PayWayCard = None + merchant: Merchant = None @staticmethod - def from_dict(response): + def from_dict(response: dict) -> PaymentSetup: """ :param: response: dict PayWay response dictionary """ @@ -460,14 +459,14 @@ def from_dict(response): return ps -class TokenResponse(object): - token = None - payment_method = None - card = None - bank_account = None +class TokenResponse: + token: str = None + payment_method: str = None + card: PayWayCard = None + bank_account: dict = None @staticmethod - def from_dict(response): + def from_dict(response: dict) -> TokenResponse: """ :param: response: dict PayWay response dictionary """ diff --git a/payway/test_utils.py b/payway/test_utils.py new file mode 100644 index 0000000..e1bd562 --- /dev/null +++ b/payway/test_utils.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import json +import pathlib + + +def load_json_file(file_path: str) -> dict: + file = pathlib.Path(file_path) + with open(file) as f: + return json.load(f) diff --git a/payway/transactions.py b/payway/transactions.py index 6775b2a..c81be4f 100644 --- a/payway/transactions.py +++ b/payway/transactions.py @@ -1,15 +1,17 @@ +from __future__ import annotations + import requests from payway.conf import TRANSACTION_URL from payway.utils import json_list -class TransactionRequest(object): +class TransactionRequest: session = requests.Session() session_no_headers = requests.Session() @json_list("search_transactions") - def search_transactions(self, query): + def search_transactions(self, query: str) -> requests.Response: """ Returns a list of transactions """ diff --git a/payway/utils.py b/payway/utils.py index 75a6262..68f5700 100644 --- a/payway/utils.py +++ b/payway/utils.py @@ -1,9 +1,13 @@ +from __future__ import annotations + +from typing import Callable + from payway.exceptions import PaywayError -def json_list(name): - def decorator(function): - def wrapper(*args, **kwargs): +def json_list(name: str) -> Callable: + def decorator(function) -> Callable: + def wrapper(*args: dict, **kwargs: dict) -> dict: result = function(*args, **kwargs) if result.status_code in [422, 404]: return result.json() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e2a6e72 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,128 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "python-payway" +version = "0.0.4" +description = "Python client for working with Westpac's PayWay REST API" +authors = [ + { name = "Ben Napper", email = "reppan197@gmail.com" } +] +license = { text = "MIT" } +readme = "README.md" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" +] +dependencies = [ + "mypy>=0.971", + "requests>=2.20.0", + "ruff==0.6.6", +] +keywords = ["payway", "westpac", "api", "client"] + +[tool.mypy] +python_version = "3.8" +check_untyped_defs = true +ignore_missing_imports = true +allow_redefinition = false +warn_unreachable = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_unused_configs = true + +[tool.ruff] +fix = true +line-length = 131 +target-version = "py38" + +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "C90", + "I", + "N", + "UP", + "YTT", + "ANN", + "ASYNC", + "S", + "BLE", + "FBT", + "B", + "A", + "COM", + "C4", + "DTZ", + "T10", + "DJ", + "EM", + "EXE", + "FA", + 'ISC', + "ICN", + "G", + 'INP', + 'PIE', + "T20", + 'PYI', + 'PT', + "Q", + "RSE", + "RET", + "SLF", + "SLOT", + "SIM", + "TID", + "INT", + "ERA", + "PD", + "PGH", + "PL", + "TRY", + "FLY", + "PERF", + "RUF", +] +ignore = ["ANN101", "COM812", "ISC001"] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401", "I"] + +[tool.ruff.lint.mccabe] +max-complexity = 5 + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +exclude = ["__init__.py"] + +[tool.ruff.lint.isort] +required-imports = ["from __future__ import annotations"] + +[tool.pyright] +typeCheckingMode = "off" + +[tool.pytest.ini_options] +log_cli = true +log_cli_level = "INFO" +log_format = "%(asctime)s %(levelname)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" +addopts = "-v -ra -q" +filterwarnings = [ + "ignore:.*defines default_app_config.*", + "ignore:The providing_args argument is deprecated..*", + "ignore::DeprecationWarning", +] + +[tool.bandit] +exclude_dirs = ["tests", "migrations"] +tests = ["B201", "B301"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 075ca84..0000000 --- a/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -requirements = [ - "requests>=2.20.0", -] - -setuptools.setup( - name="python-payway", # Replace with your own username - version="0.0.3", - author="Ben Napper", - author_email="reppan197@gmail.com", - description="Python client for working with Westpac's PayWay REST API", - long_description=long_description, - long_description_content_type="text/markdown", - license="MIT", - url="https://github.com/napper1/python-payway", - packages=setuptools.find_packages(), - install_requires=requirements, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires=">=2.7", -) diff --git a/tests/data/card_transaction.json b/tests/data/card_transaction.json new file mode 100644 index 0000000..0ef8325 --- /dev/null +++ b/tests/data/card_transaction.json @@ -0,0 +1,37 @@ +{ + "transactionId": 1179985404, + "receiptNumber": "1179985404", + "status": "approved", + "responseCode": "11", + "responseText": "Approved VIP", + "transactionType": "payment", + "customerNumber": "1", + "customerName": "Po & Sons Pty Ltd", + "customerEmail": "henry@example.net", + "currency": "aud", + "principalAmount": 100.00, + "surchargeAmount": 1.00, + "paymentAmount": 101.00, + "paymentMethod": "creditCard", + "creditCard": { + "cardNumber": "456471...004", + "expiryDateMonth": "02", + "expiryDateYear": "19", + "cardScheme": "visa", + "cardType": "credit", + "cardholderName": "Po & Sons Pty Ltd" + }, + "merchant": { + "merchantId": "4638116", + "merchantName": "MEGS BEAUTY AND NAILS", + "settlementBsb": "133-605", + "settlementAccountNumber": "172174", + "surchargeBsb": "133-606", + "surchargeAccountNumber": "172131" + }, + "transactionDateTime": "12 Jun 2015 18:22 AEST", + "user": "BILLC786", + "settlementDate": "13 Jun 2015", + "isVoidable": true, + "isRefundable": false +} \ No newline at end of file diff --git a/tests/data/customer.json b/tests/data/customer.json new file mode 100644 index 0000000..4636cbd --- /dev/null +++ b/tests/data/customer.json @@ -0,0 +1,20 @@ +{ + "customerNumber": "98", + "paymentSetup": {}, + "contact": { + "customerName": "Rebecca Turing", + "emailAddress": "bect@example.net", + "sendEmailReceipts": true, + "phoneNumber": "04 9999 8888", + "address": { + "street1": "12 Test St", + "street2": null, + "cityName": "Wombat", + "state": "NSW", + "postalCode": "2587" + } + }, + "customFields": {"customField1": "Senior"}, + "notes": {"notes": "Example notes"}, + "virtualAccount": {} +} \ No newline at end of file diff --git a/tests/data/customers.json b/tests/data/customers.json new file mode 100644 index 0000000..cdaf21e --- /dev/null +++ b/tests/data/customers.json @@ -0,0 +1,16 @@ +{ + "data": [ + { + "customerName": "Jack Smith", + "emailAddress": "jacksmith@example.com", + "phoneNumber": "0353532323", + "address": { + "street1": "3 Test St", + "street2": "Apt 1", + "cityName": "Melbourne", + "state": "VIC", + "postalCode": "3029" + } + } + ] +} \ No newline at end of file diff --git a/tests/data/refund_transaction.json b/tests/data/refund_transaction.json new file mode 100644 index 0000000..c1ccae6 --- /dev/null +++ b/tests/data/refund_transaction.json @@ -0,0 +1,34 @@ +{ + "transactionId": 1179985404, + "receiptNumber": "1179985404", + "status": "refunded", + "customerNumber": "1", + "customerName": "Po & Sons Pty Ltd", + "customerEmail": "henry@example.net", + "currency": "aud", + "principalAmount": 100.00, + "surchargeAmount": 1.00, + "paymentAmount": 101.00, + "paymentMethod": "creditCard", + "creditCard": { + "cardNumber": "456471...004", + "expiryDateMonth": "02", + "expiryDateYear": "19", + "cardScheme": "visa", + "cardType": "credit", + "cardholderName": "Po & Sons Pty Ltd" + }, + "merchant": { + "merchantId": "4638116", + "merchantName": "MEGS BEAUTY AND NAILS", + "settlementBsb": "133-605", + "settlementAccountNumber": "172174", + "surchargeBsb": "133-606", + "surchargeAccountNumber": "172131" + }, + "transactionDateTime": "12 Jun 2015 18:22 AEST", + "user": "BILLC786", + "settlementDate": "13 Jun 2015", + "isVoidable": true, + "isRefundable": false +} \ No newline at end of file diff --git a/tests/data/transaction.json b/tests/data/transaction.json new file mode 100644 index 0000000..0ef8325 --- /dev/null +++ b/tests/data/transaction.json @@ -0,0 +1,37 @@ +{ + "transactionId": 1179985404, + "receiptNumber": "1179985404", + "status": "approved", + "responseCode": "11", + "responseText": "Approved VIP", + "transactionType": "payment", + "customerNumber": "1", + "customerName": "Po & Sons Pty Ltd", + "customerEmail": "henry@example.net", + "currency": "aud", + "principalAmount": 100.00, + "surchargeAmount": 1.00, + "paymentAmount": 101.00, + "paymentMethod": "creditCard", + "creditCard": { + "cardNumber": "456471...004", + "expiryDateMonth": "02", + "expiryDateYear": "19", + "cardScheme": "visa", + "cardType": "credit", + "cardholderName": "Po & Sons Pty Ltd" + }, + "merchant": { + "merchantId": "4638116", + "merchantName": "MEGS BEAUTY AND NAILS", + "settlementBsb": "133-605", + "settlementAccountNumber": "172174", + "surchargeBsb": "133-606", + "surchargeAccountNumber": "172131" + }, + "transactionDateTime": "12 Jun 2015 18:22 AEST", + "user": "BILLC786", + "settlementDate": "13 Jun 2015", + "isVoidable": true, + "isRefundable": false +} \ No newline at end of file diff --git a/tests/data/transactions.json b/tests/data/transactions.json new file mode 100644 index 0000000..4435279 --- /dev/null +++ b/tests/data/transactions.json @@ -0,0 +1,20 @@ +{ + "data": [ + { + "transactionId": 1179985404, + "receiptNumber": "1179985404", + "status": "approved", + "responseCode": "11", + "responseText": "Approved VIP", + "transactionType": "payment", + "customerNumber": "1", + "customerName": "Po & Sons Pty Ltd", + "customerEmail": "henry@example.net", + "currency": "aud", + "principalAmount": 100.00, + "surchargeAmount": 1.00, + "paymentAmount": 101.00, + "paymentMethod": "creditCard" + } + ] +} \ No newline at end of file diff --git a/tests/data/void_transaction.json b/tests/data/void_transaction.json new file mode 100644 index 0000000..f526e5a --- /dev/null +++ b/tests/data/void_transaction.json @@ -0,0 +1,34 @@ +{ + "transactionId": 1179985404, + "receiptNumber": "1179985404", + "status": "voided", + "customerNumber": "1", + "customerName": "Po & Sons Pty Ltd", + "customerEmail": "henry@example.net", + "currency": "aud", + "principalAmount": 100.00, + "surchargeAmount": 1.00, + "paymentAmount": 101.00, + "paymentMethod": "creditCard", + "creditCard": { + "cardNumber": "456471...004", + "expiryDateMonth": "02", + "expiryDateYear": "19", + "cardScheme": "visa", + "cardType": "credit", + "cardholderName": "Po & Sons Pty Ltd" + }, + "merchant": { + "merchantId": "4638116", + "merchantName": "MEGS BEAUTY AND NAILS", + "settlementBsb": "133-605", + "settlementAccountNumber": "172174", + "surchargeBsb": "133-606", + "surchargeAccountNumber": "172131" + }, + "transactionDateTime": "12 Jun 2015 18:22 AEST", + "user": "BILLC786", + "settlementDate": "13 Jun 2015", + "isVoidable": true, + "isRefundable": false +} \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index b4344b6..54002f7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,19 +1,24 @@ +from __future__ import annotations + import copy import unittest +from typing import NoReturn from unittest.mock import patch + from payway.client import Client from payway.model import ( + BankAccount, PayWayCard, PayWayCustomer, PayWayPayment, PayWayTransaction, - BankAccount, ) +from payway.test_utils import load_json_file class TestClient(unittest.TestCase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> NoReturn: merchant_id = "TEST" bank_account_id = "0000000A" publishable_api_key = "TPUBLISHABLE-API-KEY" @@ -26,7 +31,6 @@ def setUpClass(cls): secret_api_key=secret_api_key, ) cls.customer = PayWayCustomer( - # custom_id="c981a", customer_name="John Smith", email_address="johnsmith@example.com", send_email_receipts=False, # not available in sandbox @@ -91,16 +95,16 @@ def setUpClass(cls): cls.bank_account = BankAccount( account_name="Test", bsb="000-000", - account_number=123456, + account_number="123456", ) cls.invalid_bank_account = BankAccount( account_name="Test", bsb="000-001", - account_number=123456, + account_number="123456", ) @patch("requests.post") - def test_create_token(self, mock_post): + def test_create_token(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { "singleUseTokenId": "2bcec36f-7b02-43db-b3ec-bfb65acfe272", @@ -114,7 +118,7 @@ def test_create_token(self, mock_post): self.assertEqual(token_response.token, "2bcec36f-7b02-43db-b3ec-bfb65acfe272") @patch("requests.post") - def test_create_bank_account_token(self, mock_post): + def test_create_bank_account_token(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { "singleUseTokenId": "3bcec36f-7b02-43db-b3ec-bfb65acfe272", @@ -127,24 +131,9 @@ def test_create_bank_account_token(self, mock_post): self.assertEqual(token, "3bcec36f-7b02-43db-b3ec-bfb65acfe272") @patch("requests.post") - def test_create_customer(self, mock_post): + def test_create_customer(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "customerNumber": "1", - "contact": { - "customerName": "John Smith", - "emailAddress": "johnsmith@example.com", - "sendEmailReceipts": False, - "phoneNumber": "0343232323", - "address": { - "street1": "1 Test Street", - "street2": "", - "cityName": "Sydney", - "state": "NSW", - "postalCode": "2000", - }, - }, - } + mock_post.return_value.json.return_value = load_json_file("tests/data/customer.json") card = self.card token_response, errors = self.client.create_card_token(card) customer = self.customer @@ -152,81 +141,29 @@ def test_create_customer(self, mock_post): payway_customer, customer_errors = self.client.create_customer(customer) payway_customer_number = payway_customer.customer_number self.assertIsNotNone(payway_customer_number) - self.assertEqual(payway_customer_number, "1") - self.assertEqual(payway_customer.customer_name, "John Smith") - self.assertEqual(payway_customer.email_address, "johnsmith@example.com") + self.assertEqual(payway_customer_number, "98") + self.assertEqual(payway_customer.customer_name, "Rebecca Turing") + self.assertEqual(payway_customer.email_address, "bect@example.net") @patch("requests.put") - def test_create_customer_with_custom_id(self, mock_post): + def test_create_customer_with_custom_id(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "customerNumber": "1", - "contact": { - "customerName": "John Smith", - "emailAddress": "johnsmith@example.com", - "sendEmailReceipts": False, - "phoneNumber": "0343232323", - "address": { - "street1": "1 Test Street", - "street2": "", - "cityName": "Sydney", - "state": "NSW", - "postalCode": "2000", - }, - }, - } + mock_post.return_value.json.return_value = load_json_file("tests/data/customer.json") customer = copy.deepcopy(self.customer) customer.custom_id = "a123" customer.token = "1234" payway_customer, customer_errors = self.client.create_customer(customer) payway_customer_number = payway_customer.customer_number self.assertIsNotNone(payway_customer_number) - self.assertEqual(payway_customer_number, "1") + self.assertEqual(payway_customer_number, "98") @patch("requests.post") - def test_process_payment(self, mock_post): + def test_process_payment(self, mock_post) -> NoReturn: # Take payment (using a credit card token) mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "approved", - "responseCode": "11", - "responseText": "Approved VIP", - "transactionType": "payment", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - "creditCard": { - "cardNumber": "456471...004", - "expiryDateMonth": "02", - "expiryDateYear": "19", - "cardScheme": "visa", - "cardType": "credit", - "cardholderName": "Po & Sons Pty Ltd", - }, - "merchant": { - "merchantId": "4638116", - "merchantName": "MEGS BEAUTY AND NAILS", - "settlementBsb": "133-605", - "settlementAccountNumber": "172174", - "surchargeBsb": "133-606", - "surchargeAccountNumber": "172131", - }, - "transactionDateTime": "12 Jun 2015 18:22 AEST", - "user": "BILLC786", - "settlementDate": "13 Jun 2015", - "isVoidable": True, - "isRefundable": False, - } - customer_number = "1" + mock_post.return_value.json.return_value = load_json_file("tests/data/transaction.json") payment = copy.deepcopy(self.payment) - payment.customer_number = customer_number + payment.customer_number = "1" payment.token = "2bcec36f-7b02-43db-b3ec-bfb65acfe272" payment.order_number = "5200" payment.merchant_id = self.client.merchant_id @@ -240,49 +177,14 @@ def test_process_payment(self, mock_post): self.assertEqual(transaction.response_code, "11") @patch("requests.post") - def test_process_payment_with_idempotency_key(self, mock_post): + def test_process_payment_with_idempotency_key(self, mock_post) -> NoReturn: """ Send a payment using a unique idempotency key to try and avoid duplicate POSTs https://www.payway.com.au/docs/rest.html#avoiding-duplicate-posts """ mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "approved", - "responseCode": "11", - "responseText": "Approved VIP", - "transactionType": "payment", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - "creditCard": { - "cardNumber": "456471...004", - "expiryDateMonth": "02", - "expiryDateYear": "19", - "cardScheme": "visa", - "cardType": "credit", - "cardholderName": "Po & Sons Pty Ltd", - }, - "merchant": { - "merchantId": "4638116", - "merchantName": "MEGS BEAUTY AND NAILS", - "settlementBsb": "133-605", - "settlementAccountNumber": "172174", - "surchargeBsb": "133-606", - "surchargeAccountNumber": "172131", - }, - "transactionDateTime": "12 Jun 2015 18:22 AEST", - "user": "BILLC786", - "settlementDate": "13 Jun 2015", - "isVoidable": True, - "isRefundable": False, - } + # load json file + mock_post.return_value.json.return_value = load_json_file("tests/data/transaction.json") customer_number = "1" payment = copy.deepcopy(self.payment) payment.customer_number = customer_number @@ -291,7 +193,8 @@ def test_process_payment_with_idempotency_key(self, mock_post): payment.merchant_id = self.client.merchant_id idempotency_key = "f223179b-da1d-474b-a6fc-b78bc429f76d" transaction, errors = self.client.process_payment( - payment, idempotency_key=idempotency_key + payment, + idempotency_key=idempotency_key, ) self.assertIsInstance(transaction, PayWayTransaction) self.assertIsNone(errors) @@ -301,45 +204,9 @@ def test_process_payment_with_idempotency_key(self, mock_post): self.assertEqual(transaction.response_code, "11") @patch("requests.get") - def test_get_transaction_card(self, mock_get): + def test_get_transaction_card(self, mock_get) -> NoReturn: mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "approved", - "responseCode": "11", - "responseText": "Approved VIP", - "transactionType": "payment", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - "creditCard": { - "cardNumber": "456471...004", - "expiryDateMonth": "02", - "expiryDateYear": "19", - "cardScheme": "visa", - "cardType": "credit", - "cardholderName": "Po & Sons Pty Ltd", - }, - "merchant": { - "merchantId": "4638116", - "merchantName": "MEGS BEAUTY AND NAILS", - "settlementBsb": "133-605", - "settlementAccountNumber": "172174", - "surchargeBsb": "133-606", - "surchargeAccountNumber": "172131", - }, - "transactionDateTime": "12 Jun 2015 18:22 AEST", - "user": "BILLC786", - "settlementDate": "13 Jun 2015", - "isVoidable": True, - "isRefundable": False, - } + mock_get.return_value.json.return_value = load_json_file("tests/data/card_transaction.json") transaction, errors = self.client.get_transaction( 1179985404, ) @@ -348,84 +215,18 @@ def test_get_transaction_card(self, mock_get): self.assertEqual(transaction.transaction_id, 1179985404) @patch("requests.post") - def test_void(self, mock_post): + def test_void(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "voided", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - "creditCard": { - "cardNumber": "456471...004", - "expiryDateMonth": "02", - "expiryDateYear": "19", - "cardScheme": "visa", - "cardType": "credit", - "cardholderName": "Po & Sons Pty Ltd", - }, - "merchant": { - "merchantId": "4638116", - "merchantName": "MEGS BEAUTY AND NAILS", - "settlementBsb": "133-605", - "settlementAccountNumber": "172174", - "surchargeBsb": "133-606", - "surchargeAccountNumber": "172131", - }, - "transactionDateTime": "12 Jun 2015 18:22 AEST", - "user": "BILLC786", - "settlementDate": "13 Jun 2015", - "isVoidable": True, - "isRefundable": False, - } + mock_post.return_value.json.return_value = load_json_file("tests/data/void_transaction.json") void_transaction, void_errors = self.client.void_transaction(1179985404) self.assertIsNone(void_errors) self.assertIsNotNone(void_transaction) self.assertEqual(void_transaction.status, "voided") @patch("requests.post") - def test_refund(self, mock_post): + def test_refund(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 - mock_post.return_value.json.return_value = { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "refunded", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - "creditCard": { - "cardNumber": "456471...004", - "expiryDateMonth": "02", - "expiryDateYear": "19", - "cardScheme": "visa", - "cardType": "credit", - "cardholderName": "Po & Sons Pty Ltd", - }, - "merchant": { - "merchantId": "4638116", - "merchantName": "MEGS BEAUTY AND NAILS", - "settlementBsb": "133-605", - "settlementAccountNumber": "172174", - "surchargeBsb": "133-606", - "surchargeAccountNumber": "172131", - }, - "transactionDateTime": "12 Jun 2015 18:22 AEST", - "user": "BILLC786", - "settlementDate": "13 Jun 2015", - "isVoidable": True, - "isRefundable": False, - } + mock_post.return_value.json.return_value = load_json_file("tests/data/refund_transaction.json") transaction, errors = self.client.refund_transaction( transaction_id="1179985404", amount="100", @@ -434,28 +235,9 @@ def test_refund(self, mock_post): self.assertEqual(transaction.status, "refunded") @patch("requests.get") - def test_get_customer(self, mock_get): + def test_get_customer(self, mock_get) -> NoReturn: mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = { - "customerNumber": "98", - "paymentSetup": {}, - "contact": { - "customerName": "Rebecca Turing", - "emailAddress": "bect@example.net", - "sendEmailReceipts": True, - "phoneNumber": "04 9999 8888", - "address": { - "street1": "12 Test St", - "street2": None, - "cityName": "Wombat", - "state": "NSW", - "postalCode": "2587", - }, - }, - "customFields": {"customField1": "Senior"}, - "notes": {"notes": "Example notes"}, - "virtualAccount": {}, - } + mock_get.return_value.json.return_value = load_json_file("tests/data/customer.json") customer, customer_errors = self.client.get_customer("98") self.assertIsNotNone(customer) self.assertIsNone(customer_errors) @@ -463,7 +245,7 @@ def test_get_customer(self, mock_get): self.assertEqual(customer.customer_name, "Rebecca Turing") @patch("requests.put") - def test_update_payment_setup_card(self, mock_post): + def test_update_payment_setup_card(self, mock_post) -> NoReturn: # update card or bank account in PayWay from token mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { @@ -471,7 +253,8 @@ def test_update_payment_setup_card(self, mock_post): "paymentMethod": "creditCard", } ps, ps_errors = self.client.update_payment_setup( - token="4bcec36f-7b02-43db-b3ec-bfb65acfe272", customer_id="1" + token="4bcec36f-7b02-43db-b3ec-bfb65acfe272", + customer_id="1", ) self.assertIsNone(ps_errors) self.assertIsNotNone(ps) diff --git a/tests/test_customers.py b/tests/test_customers.py index 4d1035b..d25a587 100644 --- a/tests/test_customers.py +++ b/tests/test_customers.py @@ -1,14 +1,18 @@ -import unittest +from __future__ import annotations + import datetime +import unittest +from typing import NoReturn from unittest.mock import patch from payway.client import Client -from payway.model import PayWayCard, PayWayCustomer, PayWayPayment, BankAccount +from payway.model import BankAccount, PayWayCard, PayWayCustomer, PayWayPayment +from payway.test_utils import load_json_file class TestCustomerRequest(unittest.TestCase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> NoReturn: """ You will need to create a sandbox PayWay account and add your sandbox API keys into your environment """ @@ -98,7 +102,7 @@ def setUpClass(cls): ) @patch("requests.Session.patch") - def test_stop_all_payments(self, mock_post): + def test_stop_all_payments(self, mock_post) -> NoReturn: # stop payments for customer mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { @@ -109,7 +113,7 @@ def test_stop_all_payments(self, mock_post): self.assertEqual(stopped, True) @patch("requests.Session.patch") - def test_start_all_payments(self, mock_post): + def test_start_all_payments(self, mock_post) -> NoReturn: # start payments for customer mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { @@ -120,14 +124,14 @@ def test_start_all_payments(self, mock_post): self.assertEqual(stopped, False) @patch("requests.Session.delete") - def test_delete_customer(self, mock_post): + def test_delete_customer(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 204 # delete customer record in PayWay response = self.client.delete_customer("1") self.assertEqual(response.status_code, 204) @patch("requests.Session.put") - def test_schedule_payments(self, mock_post): + def test_schedule_payments(self, mock_post) -> NoReturn: next_week = datetime.datetime.now() + datetime.timedelta(weeks=1) next_payment_date = next_week.strftime("%d %b %Y") mock_post.return_value.status_code = 200 @@ -152,14 +156,14 @@ def test_schedule_payments(self, mock_post): self.assertEqual(response["regularPaymentAmount"], 10.50) @patch("requests.Session.delete") - def test_stop_schedule(self, mock_post): + def test_stop_schedule(self, mock_post) -> NoReturn: # stop schedule mock_post.return_value.status_code = 204 response = self.client.stop_schedule("1") self.assertEqual(response.status_code, 204) @patch("requests.Session.put") - def test_update_contact_details(self, mock_post): + def test_update_contact_details(self, mock_post) -> NoReturn: # update contact details mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = { @@ -204,24 +208,9 @@ def test_update_contact_details(self, mock_post): self.assertEqual(address["postalCode"], new_postcode) @patch("requests.Session.get") - def test_list_customers(self, mock_get): + def test_list_customers(self, mock_get) -> NoReturn: mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = { - "data": [ - { - "customerName": "Jack Smith", - "emailAddress": "jacksmith@example.com", - "phoneNumber": "0353532323", - "address": { - "street1": "3 Test St", - "street2": "Apt 1", - "cityName": "Melbourne", - "state": "VIC", - "postalCode": "3029", - }, - }, - ], - } + mock_get.return_value.json.return_value = load_json_file("tests/data/customers.json") response = self.client.list_customers() self.assertEqual(response.__class__, dict) self.assertIsNotNone(response) diff --git a/tests/test_transactions.py b/tests/test_transactions.py index 5f693cb..13d4137 100644 --- a/tests/test_transactions.py +++ b/tests/test_transactions.py @@ -1,12 +1,16 @@ +from __future__ import annotations + import unittest +from typing import NoReturn from unittest.mock import patch from payway.client import Client +from payway.test_utils import load_json_file class TestTransactionRequest(unittest.TestCase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> NoReturn: merchant_id = "TEST" bank_account_id = "0000000A" publishable_api_key = "TPUBLISHABLE-API-KEY" @@ -20,28 +24,9 @@ def setUpClass(cls): ) @patch("requests.Session.get") - def test_search_transactions(self, mock_get): + def test_search_transactions(self, mock_get) -> NoReturn: mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = { - "data": [ - { - "transactionId": 1179985404, - "receiptNumber": "1179985404", - "status": "approved", - "responseCode": "11", - "responseText": "Approved VIP", - "transactionType": "payment", - "customerNumber": "1", - "customerName": "Po & Sons Pty Ltd", - "customerEmail": "henry@example.net", - "currency": "aud", - "principalAmount": 100.00, - "surchargeAmount": 1.00, - "paymentAmount": 101.00, - "paymentMethod": "creditCard", - } - ] - } + mock_get.return_value.json.return_value = load_json_file("tests/data/transactions.json") query = "/search-customer?customerNumber=1" response = self.client.search_transactions(query) transactions = response["data"] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..cab6671 --- /dev/null +++ b/uv.lock @@ -0,0 +1,151 @@ +version = 1 +requires-python = ">=3.8" + +[[package]] +name = "certifi" +version = "2024.12.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", size = 79105 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "mypy" +version = "0.971" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/66/00f7f751140fe6953603fb0cd56dee0314842cfe358884ca3025589ca81c/mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", size = 2757982 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/97/ff71b0cdf61065db040ffe34ae88852d2a47de8b2b49c51608caf03771ed/mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c", size = 18495630 }, + { url = "https://files.pythonhosted.org/packages/18/e1/d3e577229691dae4c8039cd87ef981482812ba7c5f5999fd67127af0f8a1/mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", size = 11062018 }, + { url = "https://files.pythonhosted.org/packages/f9/be/e5c50777159473c8dfb7cb512e62fbca19df4a6e9db711f17d77c14fb62b/mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", size = 10017604 }, + { url = "https://files.pythonhosted.org/packages/f0/b7/d39405fb53e0ae99c26cba3c8ab50717eafb7aeb64beea6efbd42a17ef82/mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", size = 17645516 }, + { url = "https://files.pythonhosted.org/packages/9e/01/a81de921bc3efde879f6eab5ff4d4bb33b037581f78eccfa28a47105d0b3/mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", size = 8739572 }, + { url = "https://files.pythonhosted.org/packages/77/a8/adecd715710c9338586af6a6fe66055a6b4c6799364fbe24505154ca8fd4/mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", size = 18389736 }, + { url = "https://files.pythonhosted.org/packages/4c/7f/c20f9283d6659c6ebf790cbc4c12183ceaa4adf8c03df15369c220b2c03a/mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", size = 10999183 }, + { url = "https://files.pythonhosted.org/packages/c7/13/8202db537028ac473c05f43c046bf547a0c4be0454d9ddab0c7a68525ee9/mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", size = 9974998 }, + { url = "https://files.pythonhosted.org/packages/34/6f/232461e55913d1320f33fc7e8fa8119af9db6182876093e9de189df9dbbe/mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", size = 17346581 }, + { url = "https://files.pythonhosted.org/packages/74/e5/e65b82813bdea739266e590e5fda72ec991c3e1223135342724a997fb3ff/mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", size = 8719594 }, + { url = "https://files.pythonhosted.org/packages/01/2d/7aab0a38e05dcaf14bf1f3e238f1c1a6f7bc16065eb1db8ffed62f860d27/mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", size = 18486774 }, + { url = "https://files.pythonhosted.org/packages/15/61/ffc2cf8cd1507f29444a50d0a93ecd3cc9a267c019f8c705447f40b6180d/mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", size = 11055594 }, + { url = "https://files.pythonhosted.org/packages/0b/02/644d6c498e9379f76ce5128a15f92281621770510f0fb9e321b530e1cd2c/mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", size = 10010336 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/51c1fe6dcd657fbecb130bd78ea665a26e0c44e637373de6ac141a54f83c/mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", size = 17609774 }, + { url = "https://files.pythonhosted.org/packages/77/91/53304c05871cc38e95e7e5c3e2d0f84c1822d61a731c02434a20b12ea118/mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", size = 8738328 }, + { url = "https://files.pythonhosted.org/packages/6c/c6/20dd5b70962af557101b2d3a7052f8298a3e94708b62bc5ad7ca713b59bb/mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", size = 2550912 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "python-payway" +version = "0.0.4" +source = { editable = "." } +dependencies = [ + { name = "mypy" }, + { name = "requests" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "mypy", specifier = ">=0.971" }, + { name = "requests", specifier = ">=2.20.0" }, + { name = "ruff", specifier = "==0.6.6" }, +] + +[[package]] +name = "requests" +version = "2.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d", size = 63133 }, +] + +[[package]] +name = "ruff" +version = "0.6.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/47/8220a40d1e60042d3a3a1cdf81cbafe674475cf8d60db2e28d0e4e004069/ruff-0.6.6.tar.gz", hash = "sha256:0fc030b6fd14814d69ac0196396f6761921bd20831725c7361e1b8100b818034", size = 3070828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/34/a34080926faed8ee41a4faf34e5993e367cd414cec543301113b0930f640/ruff-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:f5bc5398457484fc0374425b43b030e4668ed4d2da8ee7fdda0e926c9f11ccfb", size = 11353484 }, + { url = "https://files.pythonhosted.org/packages/22/ad/9c0b2fae42bfb54b91161b29b6b73bf73bfe7ddc03d5d3d0b4884a49df95/ruff-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:515a698254c9c47bb84335281a170213b3ee5eb47feebe903e1be10087a167ce", size = 11011998 }, + { url = "https://files.pythonhosted.org/packages/93/69/e406b534cbe2f4b992de0f4a8f9a790e4b93a3f5ca56f151e2665db95625/ruff-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6bb1b4995775f1837ab70f26698dd73852bbb82e8f70b175d2713c0354fe9182", size = 10524580 }, + { url = "https://files.pythonhosted.org/packages/26/5b/2e6fd424d78bd942dbf51f4a29512d5e698bfd9cad1245ad50ea679d5aa8/ruff-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c546f412dfae8bb9cc4f27f0e45cdd554e42fecbb34f03312b93368e1cd0a6", size = 11652644 }, + { url = "https://files.pythonhosted.org/packages/05/73/e6eab18071ac908135bcc9873c0bde240cffd8d5cfcfef8efa00eae186e4/ruff-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59627e97364329e4eae7d86fa7980c10e2b129e2293d25c478ebcb861b3e3fd6", size = 11096545 }, + { url = "https://files.pythonhosted.org/packages/63/64/e8da4e45174067e584f4d1105a160e018d8148158da7275a52f6090bd4bc/ruff-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94c3f78c3d32190aafbb6bc5410c96cfed0a88aadb49c3f852bbc2aa9783a7d8", size = 12005773 }, + { url = "https://files.pythonhosted.org/packages/2b/71/57fb76dc5a93cfa2c7d2a848d0c103e493c6553db3cbf30d642da522ee38/ruff-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:704da526c1e137f38c8a067a4a975fe6834b9f8ba7dbc5fd7503d58148851b8f", size = 12758296 }, + { url = "https://files.pythonhosted.org/packages/5a/85/583c969d1b6725aefeef2eb45e88e8ea55c30e017be8e158c322ee8bea56/ruff-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efeede5815a24104579a0f6320660536c5ffc1c91ae94f8c65659af915fb9de9", size = 12295191 }, + { url = "https://files.pythonhosted.org/packages/a6/eb/7496d2de40818ea32c0ffb75aff405b9de7dda079bcdd45952eb17216e37/ruff-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e368aef0cc02ca3593eae2fb8186b81c9c2b3f39acaaa1108eb6b4d04617e61f", size = 13402527 }, + { url = "https://files.pythonhosted.org/packages/71/27/873696e146821535c84ad2a8dc488fe78298cd0fd1a0d24a946991363b50/ruff-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2653fc3b2a9315bd809725c88dd2446550099728d077a04191febb5ea79a4f79", size = 11872520 }, + { url = "https://files.pythonhosted.org/packages/fd/88/6da14ef37b88c42191246a217c58e1d5f0a83db9748e018e94ad05936466/ruff-0.6.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bb858cd9ce2d062503337c5b9784d7b583bcf9d1a43c4df6ccb5eab774fbafcb", size = 11683880 }, + { url = "https://files.pythonhosted.org/packages/37/a3/8b9650748f72552e83f11f1d16786a24346128f4460d5b6945ed1f651901/ruff-0.6.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:488f8e15c01ea9afb8c0ba35d55bd951f484d0c1b7c5fd746ce3c47ccdedce68", size = 11186349 }, + { url = "https://files.pythonhosted.org/packages/ba/92/49523f745cf2330de1347110048cfd453de9486bef5498360595b6627074/ruff-0.6.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:aefb0bd15f1cfa4c9c227b6120573bb3d6c4ee3b29fb54a5ad58f03859bc43c6", size = 11555881 }, + { url = "https://files.pythonhosted.org/packages/99/cc/cd9ca48cb0b9b1b52710dd2f1e30c347f6cee5c455b53368665d274bfcd4/ruff-0.6.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a4c0698cc780bcb2c61496cbd56b6a3ac0ad858c966652f7dbf4ceb029252fbe", size = 11956426 }, + { url = "https://files.pythonhosted.org/packages/9d/a2/35c45a784d86daf6dab1510cbb5e572bee33b5c035e6f8e78f510c393acf/ruff-0.6.6-py3-none-win32.whl", hash = "sha256:aadf81ddc8ab5b62da7aae78a91ec933cbae9f8f1663ec0325dae2c364e4ad84", size = 9263539 }, + { url = "https://files.pythonhosted.org/packages/4a/87/c2a6fa6d1ec73a0f8b0713d69a29f8cdc17b251cd0e0ca3a96a78246ddce/ruff-0.6.6-py3-none-win_amd64.whl", hash = "sha256:0adb801771bc1f1b8cf4e0a6fdc30776e7c1894810ff3b344e50da82ef50eeb1", size = 10114810 }, + { url = "https://files.pythonhosted.org/packages/aa/b3/bfe8725d1c38addc86a2b5674ba4e3fd8ab3edb320dcd3f815b227b78b84/ruff-0.6.6-py3-none-win_arm64.whl", hash = "sha256:4b4d32c137bc781c298964dd4e52f07d6f7d57c03eae97a72d97856844aa510a", size = 9485847 }, +] + +[[package]] +name = "tomli" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/d0a8276b0cf9b9e34fd0660c330acc59656f53bb2209adc75af863a3582d/tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", size = 15094 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e4/74f9440db36734d7ba83c574c1e7024009ce849208a41f90e94a134dc6d1/tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c", size = 12122 }, +] + +[[package]] +name = "typing-extensions" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/5a/8b5fbb891ef3f81fc923bf3cb4a578c0abf9471eb50ce0f51c74212182ab/typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", size = 26694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2", size = 26844 }, +] + +[[package]] +name = "urllib3" +version = "1.26.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225 }, +] From 57970c6da2d34ad8519d3c30ca511ffd110cb2b3 Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:08:44 +1100 Subject: [PATCH 2/6] Remove mypy cache --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d554bcb..2253137 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ venv/ ENV/ env.bak/ venv.bak/ -.ruff_cache \ No newline at end of file +.ruff_cache +.mypy_cache \ No newline at end of file From 0f31fc682dd264885e9b2bd0a55f9856ad516d66 Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:25:50 +1100 Subject: [PATCH 3/6] Add pre-commit and ci --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++ .gitignore | 2 +- .pre-commit-config.ci.yaml | 34 ++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 30 ++++++++++++++++++++++++++ payway/customers.py | 4 +++- tests/data/card_transaction.json | 2 +- tests/data/customer.json | 2 +- tests/data/customers.json | 2 +- tests/data/refund_transaction.json | 2 +- tests/data/transaction.json | 2 +- tests/data/transactions.json | 2 +- tests/data/void_transaction.json | 2 +- tests/test_client.py | 2 +- 13 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.ci.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3f82747 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: Django CI/CD Workflow + +on: + release: + types: [published] + push: + branches: + - master + pull_request: + +jobs: + build: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: Set up Python 3.8 + run: uv python install 3.8.19 + - name: Install the project + run: uv sync + - name: Run pre-commit + run: uv run pre-commit run --all-files --config .pre-commit-config.ci.yaml + - name: Run tests + run: uv run python -m unittest discover tests diff --git a/.gitignore b/.gitignore index 2253137..081deee 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,4 @@ ENV/ env.bak/ venv.bak/ .ruff_cache -.mypy_cache \ No newline at end of file +.mypy_cache diff --git a/.pre-commit-config.ci.yaml b/.pre-commit-config.ci.yaml new file mode 100644 index 0000000..ac15c09 --- /dev/null +++ b/.pre-commit-config.ci.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.6.6 + hooks: + # Run linter + - id: ruff + language: system + types_or: [ python, pyi, ] + args: [--fix, --exit-non-zero-on-fix, --select, I] + # Run formatter + - id: ruff-format + language: system + types_or: [ python, pyi, ] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + exclude: "(^static/|/static/|^fobi/|.md$|.django.po$|.csv$)" + - id: end-of-file-fixer + exclude: "(^static/|/static/|^fobi/)" + - id: debug-statements + - id: check-added-large-files + - id: check-merge-conflict + - id: mixed-line-ending + args: ["--fix=lf"] + exclude: "^(static/|theme/|fixtures/static/|core/static/|fobi/)" + + - repo: https://github.com/pycqa/bandit + rev: 1.7.9 + hooks: + - id: bandit + args: ['-iii', '-ll', "-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..01b7ba1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + exclude: "(^static/|/static/|^fobi/|.md$|.django.po$|.csv$)" + - id: end-of-file-fixer + exclude: "(^static/|/static/|^fobi/)" + - id: debug-statements + - id: check-added-large-files + - id: check-merge-conflict + - id: check-json + - id: check-toml + - id: detect-private-key + - id: check-builtin-literals + - id: check-case-conflict + + - repo: https://github.com/pycqa/bandit + rev: 1.7.10 + hooks: + - id: bandit + args: ['-iii', '-ll', "-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.6.6 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format diff --git a/payway/customers.py b/payway/customers.py index 7e5e448..d36a584 100644 --- a/payway/customers.py +++ b/payway/customers.py @@ -78,7 +78,9 @@ def start_all_payments(self, customer_number: int) -> requests.Response: ) @json_list("update_contact_details") - def update_contact_details(self, customer_number: int, customer: PayWayCustomer | None = None, **options: dict) -> requests.Response: + def update_contact_details( + self, customer_number: int, customer: PayWayCustomer | None = None, **options: dict + ) -> requests.Response: """ param: customer_number: PayWay customer number param: customer PayWayCustomer object diff --git a/tests/data/card_transaction.json b/tests/data/card_transaction.json index 0ef8325..be5f773 100644 --- a/tests/data/card_transaction.json +++ b/tests/data/card_transaction.json @@ -34,4 +34,4 @@ "settlementDate": "13 Jun 2015", "isVoidable": true, "isRefundable": false -} \ No newline at end of file +} diff --git a/tests/data/customer.json b/tests/data/customer.json index 4636cbd..cf6e9bc 100644 --- a/tests/data/customer.json +++ b/tests/data/customer.json @@ -17,4 +17,4 @@ "customFields": {"customField1": "Senior"}, "notes": {"notes": "Example notes"}, "virtualAccount": {} -} \ No newline at end of file +} diff --git a/tests/data/customers.json b/tests/data/customers.json index cdaf21e..14a94f6 100644 --- a/tests/data/customers.json +++ b/tests/data/customers.json @@ -13,4 +13,4 @@ } } ] -} \ No newline at end of file +} diff --git a/tests/data/refund_transaction.json b/tests/data/refund_transaction.json index c1ccae6..2d63db7 100644 --- a/tests/data/refund_transaction.json +++ b/tests/data/refund_transaction.json @@ -31,4 +31,4 @@ "settlementDate": "13 Jun 2015", "isVoidable": true, "isRefundable": false -} \ No newline at end of file +} diff --git a/tests/data/transaction.json b/tests/data/transaction.json index 0ef8325..be5f773 100644 --- a/tests/data/transaction.json +++ b/tests/data/transaction.json @@ -34,4 +34,4 @@ "settlementDate": "13 Jun 2015", "isVoidable": true, "isRefundable": false -} \ No newline at end of file +} diff --git a/tests/data/transactions.json b/tests/data/transactions.json index 4435279..99ebbbc 100644 --- a/tests/data/transactions.json +++ b/tests/data/transactions.json @@ -17,4 +17,4 @@ "paymentMethod": "creditCard" } ] -} \ No newline at end of file +} diff --git a/tests/data/void_transaction.json b/tests/data/void_transaction.json index f526e5a..d343c11 100644 --- a/tests/data/void_transaction.json +++ b/tests/data/void_transaction.json @@ -31,4 +31,4 @@ "settlementDate": "13 Jun 2015", "isVoidable": true, "isRefundable": false -} \ No newline at end of file +} diff --git a/tests/test_client.py b/tests/test_client.py index 54002f7..64cf0c9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -163,7 +163,7 @@ def test_process_payment(self, mock_post) -> NoReturn: mock_post.return_value.status_code = 200 mock_post.return_value.json.return_value = load_json_file("tests/data/transaction.json") payment = copy.deepcopy(self.payment) - payment.customer_number = "1" + payment.customer_number = "1" payment.token = "2bcec36f-7b02-43db-b3ec-bfb65acfe272" payment.order_number = "5200" payment.merchant_id = self.client.merchant_id From 07d8d7a787b8d51c509fb87eb15c8cd2e98b97ab Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:45:14 +1100 Subject: [PATCH 4/6] Try again --- .github/workflows/ci.yml | 4 ++-- README.md | 2 +- pyproject.toml | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f82747..5546dbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Django CI/CD Workflow +name: CI Workflow on: release: @@ -22,7 +22,7 @@ jobs: - name: Set up Python 3.8 run: uv python install 3.8.19 - name: Install the project - run: uv sync + run: uv sync --extra dev - name: Run pre-commit run: uv run pre-commit run --all-files --config .pre-commit-config.ci.yaml - name: Run tests diff --git a/README.md b/README.md index c1aa9e4..0bb68b3 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction uv python install 3.8.19 uv venv source .venv/bin/activate -uv sync +uv sync --extra dev ``` ## Testing diff --git a/pyproject.toml b/pyproject.toml index e2a6e72..635fc3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,9 +18,13 @@ classifiers = [ "Operating System :: OS Independent" ] dependencies = [ - "mypy>=0.971", "requests>=2.20.0", - "ruff==0.6.6", +] +[project.optional-dependencies] +dev = [ + "pre-commit>=3.5.0", + "ruff==0.6.6", + "mypy>=0.971", ] keywords = ["payway", "westpac", "api", "client"] From 76e78599b10d6fa653eb0182c817d0c022a6a6bf Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:53:52 +1100 Subject: [PATCH 5/6] And again --- pyproject.toml | 10 ++- uv.lock | 162 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 635fc3d..15f072e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,16 +17,17 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent" ] +keywords = ["payway", "westpac", "api", "client"] dependencies = [ "requests>=2.20.0", ] + [project.optional-dependencies] dev = [ - "pre-commit>=3.5.0", + # "pre-commit>=3.5.0", "ruff==0.6.6", "mypy>=0.971", ] -keywords = ["payway", "westpac", "api", "client"] [tool.mypy] python_version = "3.8" @@ -130,3 +131,8 @@ filterwarnings = [ [tool.bandit] exclude_dirs = ["tests", "migrations"] tests = ["B201", "B301"] + +[dependency-groups] +dev = [ + "pre-commit>=3.5.0", +] diff --git a/uv.lock b/uv.lock index cab6671..9a41067 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + [[package]] name = "charset-normalizer" version = "2.0.12" @@ -19,6 +28,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623 }, ] +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "identify" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972 }, +] + [[package]] name = "idna" version = "3.10" @@ -66,21 +102,127 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pre-commit" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b3/4ae08d21eb097162f5aad37f4585f8069a86402ed7f5362cc9ae097f9572/pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32", size = 177079 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/75/526915fedf462e05eeb1c75ceaf7e3f9cde7b5ce6f62740fe5f7f19a0050/pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660", size = 203698 }, +] + [[package]] name = "python-payway" version = "0.0.4" source = { editable = "." } dependencies = [ - { name = "mypy" }, { name = "requests" }, +] + +[package.optional-dependencies] +dev = [ + { name = "mypy" }, { name = "ruff" }, ] +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, +] + [package.metadata] requires-dist = [ - { name = "mypy", specifier = ">=0.971" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=0.971" }, { name = "requests", specifier = ">=2.20.0" }, - { name = "ruff", specifier = "==0.6.6" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.6.6" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pre-commit", specifier = ">=3.5.0" }] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218 }, + { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067 }, + { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812 }, + { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531 }, + { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820 }, + { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514 }, + { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, ] [[package]] @@ -149,3 +291,17 @@ sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc5 wheels = [ { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225 }, ] + +[[package]] +name = "virtualenv" +version = "20.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, +] From 21e1efee02d40b24766ed74fe45767bf95346848 Mon Sep 17 00:00:00 2001 From: Ben Napper Date: Tue, 31 Dec 2024 14:55:08 +1100 Subject: [PATCH 6/6] Woops --- pyproject.toml | 7 +------ uv.lock | 10 ++-------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 15f072e..5850806 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ [project.optional-dependencies] dev = [ - # "pre-commit>=3.5.0", + "pre-commit>=3.5.0", "ruff==0.6.6", "mypy>=0.971", ] @@ -131,8 +131,3 @@ filterwarnings = [ [tool.bandit] exclude_dirs = ["tests", "migrations"] tests = ["B201", "B301"] - -[dependency-groups] -dev = [ - "pre-commit>=3.5.0", -] diff --git a/uv.lock b/uv.lock index 9a41067..825283a 100644 --- a/uv.lock +++ b/uv.lock @@ -147,24 +147,18 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "mypy" }, - { name = "ruff" }, -] - -[package.dev-dependencies] -dev = [ { name = "pre-commit" }, + { name = "ruff" }, ] [package.metadata] requires-dist = [ { name = "mypy", marker = "extra == 'dev'", specifier = ">=0.971" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" }, { name = "requests", specifier = ">=2.20.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = "==0.6.6" }, ] -[package.metadata.requires-dev] -dev = [{ name = "pre-commit", specifier = ">=3.5.0" }] - [[package]] name = "pyyaml" version = "6.0.2"