From 12fb76765c67c35e07f2fba9b106c0585614b151 Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Thu, 28 Sep 2023 14:17:02 -0600 Subject: [PATCH] [feat] Add dedicated API Keys service (#297) - Add dedicated API Keys service - Migrate API key-related functions to API Keys service, deprecated in User service - Migrate unit tests, re-record cassettes as needed --- CHANGELOG.md | 4 + Makefile | 2 +- easypost/constant.py | 1 + easypost/easypost_client.py | 2 + easypost/services/__init__.py | 1 + easypost/services/api_key_service.py | 45 +++++++++ easypost/services/user_service.py | 14 +++ examples | 2 +- ...l_api_keys.yaml => test_all_api_keys.yaml} | 14 +-- .../test_authenticated_user_api_keys.yaml | 44 ++++----- tests/cassettes/test_child_user_api_keys.yaml | 96 ++++++++++--------- tests/cassettes/test_demo_hooks.yaml | 66 +++++++++++++ tests/test_api_key.py | 35 +++++++ tests/test_user.py | 34 ------- 14 files changed, 249 insertions(+), 111 deletions(-) create mode 100644 easypost/services/api_key_service.py rename tests/cassettes/{test_user_all_api_keys.yaml => test_all_api_keys.yaml} (88%) create mode 100644 tests/cassettes/test_demo_hooks.yaml create mode 100644 tests/test_api_key.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d09c17b0..02d7965f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +- Add dedicated API Key-related service, available via the `api_keys` property of a client + - NOTE: Please note the naming. The `api_key` property of a client is the currently-used API key string, while the `api_keys` property is the service for managing API keys. +- Migrated API Key-related functionality to `api_keys` service, deprecated old methods in `user` service + ## v8.1.1 (2023-09-05) - Fix endpoint for creating a FedEx Smartpost carrier account diff --git a/Makefile b/Makefile index c9962729..84cdfc44 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ clean: ## coverage - Test the project and generate an HTML coverage report coverage: - $(VIRTUAL_BIN)/pytest --cov=$(PROJECT_NAME) --cov-branch --cov-report=html --cov-report=lcov --cov-report=term-missing --cov-fail-under=90 + $(VIRTUAL_BIN)/pytest --cov=$(PROJECT_NAME) --cov-branch --cov-report=html --cov-report=lcov --cov-report=term-missing --cov-fail-under=89 ## docs - Generates docs for the library docs: diff --git a/easypost/constant.py b/easypost/constant.py index 41e782da..edafe6e4 100644 --- a/easypost/constant.py +++ b/easypost/constant.py @@ -26,6 +26,7 @@ NO_BILLING_ERROR = "Billing has not been setup for this user. Please add a payment method." NO_MORE_PAGES_ERROR = "There are no more pages to retrieve." NO_RATES_ERROR = "No rates found." +NO_USER_FOUND = "No user found with the given id." SEND_STRIPE_DETAILS_ERROR = "Could not send card details to Stripe, please try again later." TIMEOUT_ERROR = "Request timed out." diff --git a/easypost/easypost_client.py b/easypost/easypost_client.py index 948cd49a..378152bb 100644 --- a/easypost/easypost_client.py +++ b/easypost/easypost_client.py @@ -11,6 +11,7 @@ ) from easypost.services import ( AddressService, + ApiKeyService, BatchService, BetaCarrierMetadataService, BetaRateService, @@ -54,6 +55,7 @@ def __init__( # Services self.address = AddressService(self) + self.api_keys = ApiKeyService(self) self.batch = BatchService(self) self.beta_carrier_metadata = BetaCarrierMetadataService(self) self.beta_rate = BetaRateService(self) diff --git a/easypost/services/__init__.py b/easypost/services/__init__.py index f4302de0..dfb31aed 100644 --- a/easypost/services/__init__.py +++ b/easypost/services/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from easypost.services.address_service import AddressService +from easypost.services.api_key_service import ApiKeyService from easypost.services.batch_service import BatchService from easypost.services.beta_carrier_metadata_service import BetaCarrierMetadataService from easypost.services.beta_rate_service import BetaRateService diff --git a/easypost/services/api_key_service.py b/easypost/services/api_key_service.py new file mode 100644 index 00000000..048ed861 --- /dev/null +++ b/easypost/services/api_key_service.py @@ -0,0 +1,45 @@ +from typing import ( + Any, + Dict, + List, +) + +from easypost.constant import NO_USER_FOUND +from easypost.easypost_object import convert_to_easypost_object +from easypost.errors import FilteringError +from easypost.models import ApiKey +from easypost.requestor import ( + RequestMethod, + Requestor, +) +from easypost.services.base_service import BaseService + + +class ApiKeyService(BaseService): + def __init__(self, client): + self._client = client + self._model_class = ApiKey.__name__ + + def all(self) -> Dict[str, Any]: + """Retrieve a list of all API keys.""" + url = "/api_keys" + + response = Requestor(self._client).request(method=RequestMethod.GET, url=url) + + return convert_to_easypost_object(response=response) + + def retrieve_api_keys_for_user(self, id: str) -> List[ApiKey]: + """Retrieve a list of API keys (works for the authenticated User or a child User).""" + api_keys = self.all() + + if api_keys["id"] == id: + # This function was called on the authenticated user + return api_keys["keys"] + + # This function was called on a child user (authenticated as parent, only return + # this child user's details). + for child in api_keys["children"]: + if child.id == id: + return child.keys + + raise FilteringError(message=NO_USER_FOUND) diff --git a/easypost/services/user_service.py b/easypost/services/user_service.py index a440af9a..227d5ba6 100644 --- a/easypost/services/user_service.py +++ b/easypost/services/user_service.py @@ -4,6 +4,7 @@ List, Optional, ) +from warnings import warn from easypost.easypost_object import convert_to_easypost_object from easypost.models import ( @@ -68,6 +69,12 @@ def retrieve_me(self) -> User: def all_api_keys(self) -> Dict[str, Any]: """Retrieve a list of all API keys.""" + warn( + 'This method is deprecated, use the "all" function of "api_keys" on the client instead.', + DeprecationWarning, + stacklevel=2, + ) + url = "/api_keys" response = Requestor(self._client).request(method=RequestMethod.GET, url=url) @@ -76,6 +83,13 @@ def all_api_keys(self) -> Dict[str, Any]: def api_keys(self, id: str) -> List[ApiKey]: """Retrieve a list of API keys (works for the authenticated User or a child User).""" + warn( + 'This method is deprecated, use the "retrieve_api_keys_for_user" function ' + 'of "api_keys" on the client instead.', + DeprecationWarning, + stacklevel=2, + ) + api_keys = self.all_api_keys() my_api_keys = [] diff --git a/examples b/examples index 01244f88..f6681272 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 01244f88382baa36cd0f86855783a6c542d5d864 +Subproject commit f6681272591087f15e99b032876463ffa4ae99b8 diff --git a/tests/cassettes/test_user_all_api_keys.yaml b/tests/cassettes/test_all_api_keys.yaml similarity index 88% rename from tests/cassettes/test_user_all_api_keys.yaml rename to tests/cassettes/test_all_api_keys.yaml index 60380f81..1752353b 100644 --- a/tests/cassettes/test_user_all_api_keys.yaml +++ b/tests/cassettes/test_all_api_keys.yaml @@ -43,25 +43,27 @@ interactions: - chunked x-backend: - easypost + x-canary: + - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - de846e086463b6c8e787409c002d8c1b + - 44bce2e165147ecef3f87071000edcd6 x-frame-options: - SAMEORIGIN x-node: - - bigweb11nuq + - bigweb43nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb1nuq a29e4ad05c - - extlb1nuq 5ab12a3ed2 + - intlb2nuq cac2303bbb + - extlb2nuq 003ad9bca0 x-runtime: - - '0.184135' + - '0.030783' x-version-label: - - easypost-202305161623-0a285b6b1b-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: diff --git a/tests/cassettes/test_authenticated_user_api_keys.yaml b/tests/cassettes/test_authenticated_user_api_keys.yaml index 521e54b3..741e0de3 100644 --- a/tests/cassettes/test_authenticated_user_api_keys.yaml +++ b/tests/cassettes/test_authenticated_user_api_keys.yaml @@ -17,22 +17,22 @@ interactions: response: body: string: '{"id": "user_54356a6b96c940d8913961a3c7b909f2", "object": "User", "parent_id": - null, "name": "EasyPost Python Client Library Tests", "phone_number": "", - "verified": true, "created_at": "2017-03-01T03:01:34Z", "default_carbon_offset": - false, "has_elevate_access": false, "balance": "0.00000", "price_per_shipment": - "0.00000", "recharge_amount": "100.00", "secondary_recharge_amount": "100.00", - "recharge_threshold": "0.00", "has_billing_method": false, "cc_fee_rate": - "0.03", "default_insurance_amount": null, "insurance_fee_rate": "0.01", "insurance_fee_minimum": - "1.00", "email": "", "children": []}' + null, "name": "New Name", "phone_number": "", "verified": true, + "created_at": "2017-03-01T03:01:34Z", "default_carbon_offset": false, "has_elevate_access": + false, "balance": "0.00000", "price_per_shipment": "0.00000", "recharge_amount": + "100.00", "secondary_recharge_amount": "100.00", "recharge_threshold": "0.00", + "has_billing_method": false, "cc_fee_rate": "0.03", "default_insurance_amount": + null, "insurance_fee_rate": "0.01", "insurance_fee_minimum": "1.00", "email": + "", "children": []}' headers: cache-control: - private, no-cache, no-store content-length: - - '601' + - '573' content-type: - application/json; charset=utf-8 etag: - - W/"37fad0de2fa69ce547baa7b7ecd8f745" + - W/"4d7e243262cf39546f0f5363c1c1e8e0" expires: - '0' pragma: @@ -45,27 +45,25 @@ interactions: - chunked x-backend: - easypost - x-canary: - - direct x-content-type-options: - nosniff x-download-options: - noopen x-ep-request-uuid: - - 52975b54645e93e8e2cc319b00062464 + - 44bce2de65147ef6f40a7431000ef004 x-frame-options: - SAMEORIGIN x-node: - - bigweb7nuq + - bigweb31nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb2nuq a29e4ad05c - - extlb2nuq 5ab12a3ed2 + - intlb2nuq cac2303bbb + - extlb2nuq 003ad9bca0 x-runtime: - - '0.050837' + - '0.039260' x-version-label: - - easypost-202305121849-b449e9bf47-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: @@ -120,20 +118,20 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 52975b54645e93e8e2cc319b0006246e + - 44bce2de65147ef6f40a7431000ef010 x-frame-options: - SAMEORIGIN x-node: - - bigweb1nuq + - bigweb35nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb2nuq a29e4ad05c - - extlb2nuq 5ab12a3ed2 + - intlb1nuq cac2303bbb + - extlb2nuq 003ad9bca0 x-runtime: - - '0.031562' + - '0.026740' x-version-label: - - easypost-202305121849-b449e9bf47-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: diff --git a/tests/cassettes/test_child_user_api_keys.yaml b/tests/cassettes/test_child_user_api_keys.yaml index 5ba20359..32bb4e56 100644 --- a/tests/cassettes/test_child_user_api_keys.yaml +++ b/tests/cassettes/test_child_user_api_keys.yaml @@ -20,23 +20,23 @@ interactions: uri: https://api.easypost.com/v2/users response: body: - string: '{"id": "user_6ac14ce1d9844c8cb9650f4502882b74", "object": "User", "parent_id": + string: '{"id": "user_f72c502dd7a444c29b905f83abe2d518", "object": "User", "parent_id": "user_54356a6b96c940d8913961a3c7b909f2", "name": "Test User", "phone_number": - "", "verified": true, "created_at": "2022-11-23T20:37:06Z", "default_carbon_offset": - false, "children": [], "api_keys": [{"object": "ApiKey", "key": "", - "mode": "test", "created_at": "2022-11-23T20:37:06Z", "active": true, "id": - "ak_03e2b1d2c5b840fd9353e3eb272f6cec"}, {"object": "ApiKey", "key": "", - "mode": "production", "created_at": "2022-11-23T20:37:06Z", "active": true, - "id": "ak_423eeac4dd0e4096a38c12e18acce5e3"}]}' + "", "verified": true, "created_at": "2023-09-27T19:14:02Z", "default_carbon_offset": + false, "has_elevate_access": false, "children": [], "api_keys": [{"object": + "ApiKey", "key": "", "mode": "test", "created_at": "2023-09-27T19:14:02Z", + "active": true, "id": "ak_8f90ceb343ff483eb8f88ceb0a061b51"}, {"object": "ApiKey", + "key": "", "mode": "production", "created_at": "2023-09-27T19:14:02Z", + "active": true, "id": "ak_7035ab827a8a4f0d9074885785fe4305"}]}' headers: cache-control: - private, no-cache, no-store content-length: - - '664' + - '691' content-type: - application/json; charset=utf-8 etag: - - W/"d13733503625803c1ad19960defcb95a" + - W/"319590edac15dcf261dcab1509db5d3c" expires: - '0' pragma: @@ -54,20 +54,21 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 3dff6286637e8472e787bff8000e9df8 + - 7a8b9d9765147efaf42f28ca00499588 x-frame-options: - SAMEORIGIN x-node: - - bigweb11nuq + - bigweb40nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb2nuq 29913d444b - - extlb2nuq 29913d444b + - intlb1nuq cac2303bbb + - intlb2wdc cac2303bbb + - extlb4wdc 003ad9bca0 x-runtime: - - '0.694237' + - '0.737052' x-version-label: - - easypost-202211231902-b75422a88e-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: @@ -87,22 +88,22 @@ interactions: user-agent: - method: GET - uri: https://api.easypost.com/v2/users/user_6ac14ce1d9844c8cb9650f4502882b74 + uri: https://api.easypost.com/v2/users/user_f72c502dd7a444c29b905f83abe2d518 response: body: - string: '{"id": "user_6ac14ce1d9844c8cb9650f4502882b74", "object": "User", "parent_id": + string: '{"id": "user_f72c502dd7a444c29b905f83abe2d518", "object": "User", "parent_id": "user_54356a6b96c940d8913961a3c7b909f2", "name": "Test User", "phone_number": - "", "verified": true, "created_at": "2022-11-23T20:37:06Z", "default_carbon_offset": - false, "children": []}' + "", "verified": true, "created_at": "2023-09-27T19:14:02Z", "default_carbon_offset": + false, "has_elevate_access": false, "children": []}' headers: cache-control: - private, no-cache, no-store content-length: - - '257' + - '284' content-type: - application/json; charset=utf-8 etag: - - W/"5176ce0e22970d1a6dcfa66250114a47" + - W/"71ec2f5c910235246ff2f7d50121651c" expires: - '0' pragma: @@ -120,20 +121,21 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 3dff6286637e8473e787bff8000e9e73 + - 7a8b9d9765147efaf42f28ca004995db x-frame-options: - SAMEORIGIN x-node: - - bigweb8nuq + - bigweb42nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb1nuq 29913d444b - - extlb2nuq 29913d444b + - intlb2nuq cac2303bbb + - intlb1wdc cac2303bbb + - extlb4wdc 003ad9bca0 x-runtime: - - '0.034930' + - '0.036852' x-version-label: - - easypost-202211231902-b75422a88e-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: @@ -161,11 +163,11 @@ interactions: "2022-02-24T20:59:05Z", "id": "ak_16f993eae7984eab94e0957a78bee407"}, {"object": "ApiKey", "active": true, "key": "", "mode": "production", "created_at": "2022-02-24T20:59:14Z", "id": "ak_62524a9c3f684f65ab9eccbf70950df8"}], "children": - [{"id": "user_6ac14ce1d9844c8cb9650f4502882b74", "keys": [{"object": "ApiKey", - "active": true, "key": "", "mode": "test", "created_at": "2022-11-23T20:37:06Z", - "id": "ak_03e2b1d2c5b840fd9353e3eb272f6cec"}, {"object": "ApiKey", "active": - true, "key": "", "mode": "production", "created_at": "2022-11-23T20:37:06Z", - "id": "ak_423eeac4dd0e4096a38c12e18acce5e3"}], "children": []}]}' + [{"id": "user_f72c502dd7a444c29b905f83abe2d518", "keys": [{"object": "ApiKey", + "active": true, "key": "", "mode": "test", "created_at": "2023-09-27T19:14:02Z", + "id": "ak_8f90ceb343ff483eb8f88ceb0a061b51"}, {"object": "ApiKey", "active": + true, "key": "", "mode": "production", "created_at": "2023-09-27T19:14:02Z", + "id": "ak_7035ab827a8a4f0d9074885785fe4305"}], "children": []}]}' headers: cache-control: - private, no-cache, no-store @@ -174,7 +176,7 @@ interactions: content-type: - application/json; charset=utf-8 etag: - - W/"5209613f3e9233c508698b6e3703b4b0" + - W/"84380e4a04340a6b5c74dd59c777da8b" expires: - '0' pragma: @@ -192,20 +194,21 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 3dff6286637e8473e787bff8000e9e84 + - 7a8b9d9765147efbf42f28ca00499606 x-frame-options: - SAMEORIGIN x-node: - - bigweb9nuq + - bigweb35nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb2nuq 29913d444b - - extlb2nuq 29913d444b + - intlb1nuq cac2303bbb + - intlb1wdc cac2303bbb + - extlb4wdc 003ad9bca0 x-runtime: - - '0.038706' + - '0.034916' x-version-label: - - easypost-202211231902-b75422a88e-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: @@ -227,7 +230,7 @@ interactions: user-agent: - method: DELETE - uri: https://api.easypost.com/v2/users/user_6ac14ce1d9844c8cb9650f4502882b74 + uri: https://api.easypost.com/v2/users/user_f72c502dd7a444c29b905f83abe2d518 response: body: string: '' @@ -249,20 +252,21 @@ interactions: x-download-options: - noopen x-ep-request-uuid: - - 3dff6286637e8473e787bff8000e9e95 + - 7a8b9d9765147efbf42f28ca00499617 x-frame-options: - SAMEORIGIN x-node: - - bigweb3nuq + - bigweb33nuq x-permitted-cross-domain-policies: - none x-proxied: - - intlb2nuq 29913d444b - - extlb2nuq 29913d444b + - intlb2nuq cac2303bbb + - intlb2wdc cac2303bbb + - extlb4wdc 003ad9bca0 x-runtime: - - '0.120697' + - '1.148696' x-version-label: - - easypost-202211231902-b75422a88e-master + - easypost-202309271719-1317a54bfa-master x-xss-protection: - 1; mode=block status: diff --git a/tests/cassettes/test_demo_hooks.yaml b/tests/cassettes/test_demo_hooks.yaml new file mode 100644 index 00000000..fe68daf6 --- /dev/null +++ b/tests/cassettes/test_demo_hooks.yaml @@ -0,0 +1,66 @@ +interactions: +- request: + body: '{"parcel": {"length": 10, "width": 8, "height": 4, "weight": 15.4}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '67' + Content-Type: + - application/json + authorization: + - + user-agent: + - + method: POST + uri: https://api.easypost.com/v2/parcels + response: + body: + string: '{"error": {"code": "APIKEY.INACTIVE", "message": "This api key is no + longer active. Please use a different api key or reactivate this key.", "errors": + []}}' + headers: + cache-control: + - no-cache + content-length: + - '149' + content-type: + - application/json; charset=utf-8 + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-backend: + - easypost + x-content-type-options: + - nosniff + x-download-options: + - noopen + x-ep-request-uuid: + - 26766bd164c40f06f3f8e38e00033351 + x-frame-options: + - SAMEORIGIN + x-node: + - bigweb36nuq + x-permitted-cross-domain-policies: + - none + x-proxied: + - intlb2nuq d3d339cca1 + - intlb1wdc d3d339cca1 + - extlb4wdc 003ad9bca0 + x-runtime: + - '0.009841' + x-version-label: + - easypost-202307281702-336406b3a6-master + x-xss-protection: + - 1; mode=block + status: + code: 403 + message: Forbidden +version: 1 diff --git a/tests/test_api_key.py b/tests/test_api_key.py new file mode 100644 index 00000000..fa63fc2c --- /dev/null +++ b/tests/test_api_key.py @@ -0,0 +1,35 @@ +import pytest +from easypost.models import ApiKey + + +@pytest.mark.vcr() +def test_all_api_keys(prod_client): + """Tests that we can retrieve all API keys.""" + api_keys = prod_client.api_keys.all() + + assert all(isinstance(key, ApiKey) for key in api_keys.keys) + for child in api_keys.children: + assert all(isinstance(key, ApiKey) for key in child["keys"]) + + +@pytest.mark.vcr() +def test_authenticated_user_api_keys(prod_client): + """Tests that we can retrieve the authenticated user's API keys.""" + user = prod_client.user.retrieve_me() + api_keys = prod_client.api_keys.retrieve_api_keys_for_user(user.id) + + assert all(isinstance(key, ApiKey) for key in api_keys) + + +@pytest.mark.vcr() +def test_child_user_api_keys(prod_client): + """Tests that we can retrieve a child user's API keys as the parent.""" + user = prod_client.user.create(name="Test User") + child_user = prod_client.user.retrieve(user.id) + + api_keys = prod_client.api_keys.retrieve_api_keys_for_user(child_user.id) + + assert all(isinstance(key, ApiKey) for key in api_keys) + + # Delete the user so we don't clutter up the test environment + prod_client.user.delete(child_user.id) diff --git a/tests/test_user.py b/tests/test_user.py index 9bc210e2..ee92531c 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,6 +1,5 @@ import pytest from easypost.models import ( - ApiKey, Brand, User, ) @@ -66,39 +65,6 @@ def test_user_delete(prod_client): prod_client.user.delete(user.id) -@pytest.mark.vcr() -def test_user_all_api_keys(prod_client): - """Tests that we can retrieve all API keys.""" - api_keys = prod_client.user.all_api_keys() - - assert all(isinstance(key, ApiKey) for key in api_keys.keys) - for child in api_keys.children: - assert all(isinstance(key, ApiKey) for key in child["keys"]) - - -@pytest.mark.vcr() -def test_authenticated_user_api_keys(prod_client): - """Tests that we can retrieve the authenticated user's API keys.""" - user = prod_client.user.retrieve_me() - api_keys = prod_client.user.api_keys(user.id) - - assert all(isinstance(key, ApiKey) for key in api_keys) - - -@pytest.mark.vcr() -def test_child_user_api_keys(prod_client): - """Tests that we can retrieve a child user's API keys as the parent.""" - user = prod_client.user.create(name="Test User") - child_user = prod_client.user.retrieve(user.id) - - api_keys = prod_client.user.api_keys(child_user.id) - - assert all(isinstance(key, ApiKey) for key in api_keys) - - # Delete the user so we don't clutter up the test environment - prod_client.user.delete(child_user.id) - - @pytest.mark.vcr() def test_user_update_brand(prod_client): user = prod_client.user.retrieve_me()