diff --git a/CHANGES.md b/CHANGES.md index af099bef92..1cd9a3dac4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,19 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +[2023-10-05] Version 8.9.1 +-------------------------- +**Library - Chore** +- [PR #721](https://github.com/twilio/twilio-python/pull/721): Drop dependency on `pytz` by using stdlib `datetime.timezone.utc`. Thanks to [@Zac-HD](https://github.com/Zac-HD)! +- [PR #723](https://github.com/twilio/twilio-python/pull/723): twilio help changes. Thanks to [@kridai](https://github.com/kridai)! + +**Library - Fix** +- [PR #724](https://github.com/twilio/twilio-python/pull/724): Update ValidateSslCertificate method. Thanks to [@AsabuHere](https://github.com/AsabuHere)! + +**Lookups** +- Add test api support for Lookup v2 + + [2023-09-21] Version 8.9.0 -------------------------- **Conversations** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a76a0bf826..41cb4474d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ it can be. If you have questions about how to use `twilio-python`, please see our [docs](./README.md), and if you don't find the answer there, please contact -[help@twilio.com](mailto:help@twilio.com) with any issues you have. +[Twilio Support](https://www.twilio.com/help/contact) with any issues you have. ## Found an Issue? diff --git a/LICENSE b/LICENSE index ca16167a61..6485c1f845 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2023, Twilio, Inc. +Copyright (C) 2023, Twilio, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/requirements.txt b/requirements.txt index 71730f9278..a931dbd88a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability -pytz requests>=2.0.0 PyJWT>=2.0.0, <3.0.0 aiohttp>=3.8.4 diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 98a71fd7e4..ae915bb848 --- a/setup.py +++ b/setup.py @@ -13,15 +13,14 @@ setup( name="twilio", - version="8.9.0", + version="8.9.1", description="Twilio API client and TwiML generator", author="Twilio", - author_email="help@twilio.com", + help_center="https://www.twilio.com/help/contact", url="https://github.com/twilio/twilio-python/", keywords=["twilio", "twiml"], python_requires=">=3.7.0", install_requires=[ - "pytz", "requests >= 2.0.0", "PyJWT >= 2.0.0, < 3.0.0", "aiohttp>=3.8.4", diff --git a/tests/unit/base/test_deserialize.py b/tests/unit/base/test_deserialize.py index 9953772471..33f627e838 100644 --- a/tests/unit/base/test_deserialize.py +++ b/tests/unit/base/test_deserialize.py @@ -2,8 +2,6 @@ import unittest from decimal import Decimal -import pytz - from twilio.base import deserialize @@ -21,7 +19,7 @@ def test_not_parsable(self): class Iso8601DateTimeTestCase(unittest.TestCase): def test_parsable(self): actual = deserialize.iso8601_datetime("2015-01-02T03:04:05Z") - expected = datetime.datetime(2015, 1, 2, 3, 4, 5, 0, pytz.utc) + expected = datetime.datetime(2015, 1, 2, 3, 4, 5, 0, datetime.timezone.utc) self.assertEqual(expected, actual) def test_not_parsable(self): diff --git a/twilio/__init__.py b/twilio/__init__.py index 43ea11725b..f36b8434e9 100644 --- a/twilio/__init__.py +++ b/twilio/__init__.py @@ -1,2 +1,2 @@ -__version_info__ = ("8", "9", "0") +__version_info__ = ("8", "9", "1") __version__ = ".".join(__version_info__) diff --git a/twilio/base/deserialize.py b/twilio/base/deserialize.py index e70ac57390..71226c08f8 100644 --- a/twilio/base/deserialize.py +++ b/twilio/base/deserialize.py @@ -3,13 +3,11 @@ from email.utils import parsedate from typing import Optional, Union -import pytz - ISO8601_DATE_FORMAT = "%Y-%m-%d" ISO8601_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" -def iso8601_date(s: str) -> Optional[Union[datetime.date, str]]: +def iso8601_date(s: str) -> Union[datetime.date, str]: """ Parses an ISO 8601 date string and returns a UTC date object or the string if the parsing failed. @@ -19,7 +17,7 @@ def iso8601_date(s: str) -> Optional[Union[datetime.date, str]]: try: return ( datetime.datetime.strptime(s, ISO8601_DATE_FORMAT) - .replace(tzinfo=pytz.utc) + .replace(tzinfo=datetime.timezone.utc) .date() ) except (TypeError, ValueError): @@ -28,7 +26,7 @@ def iso8601_date(s: str) -> Optional[Union[datetime.date, str]]: def iso8601_datetime( s: str, -) -> Optional[Union[datetime.datetime, str]]: +) -> Union[datetime.datetime, str]: """ Parses an ISO 8601 datetime string and returns a UTC datetime object, or the string if parsing failed. @@ -36,7 +34,7 @@ def iso8601_datetime( """ try: return datetime.datetime.strptime(s, ISO8601_DATETIME_FORMAT).replace( - tzinfo=pytz.utc + tzinfo=datetime.timezone.utc ) except (TypeError, ValueError): return s @@ -52,10 +50,10 @@ def rfc2822_datetime(s: str) -> Optional[datetime.datetime]: date_tuple = parsedate(s) if date_tuple is None: return None - return datetime.datetime(*date_tuple[:6]).replace(tzinfo=pytz.utc) + return datetime.datetime(*date_tuple[:6]).replace(tzinfo=datetime.timezone.utc) -def decimal(d: Optional[str]) -> Optional[Union[Decimal, str]]: +def decimal(d: Optional[str]) -> Union[Decimal, str]: """ Parses a decimal string into a Decimal :param d: decimal string @@ -65,7 +63,7 @@ def decimal(d: Optional[str]) -> Optional[Union[Decimal, str]]: return Decimal(d, BasicContext) -def integer(i: str) -> Optional[Union[int, str]]: +def integer(i: str) -> Union[int, str]: """ Parses an integer string into an int :param i: integer string diff --git a/twilio/http/validation_client.py b/twilio/http/validation_client.py index c8e211ddf1..1a4a83f0a5 100644 --- a/twilio/http/validation_client.py +++ b/twilio/http/validation_client.py @@ -128,11 +128,11 @@ def validate_ssl_certificate(self, client): Validate that a request to the new SSL certificate is successful :return: null on success, raise TwilioRestException if the request fails """ - response = client.request("GET", "https://api.twilio.com:8443") + response = client.request("GET", "https://tls-test.twilio.com:443") if response.status_code < 200 or response.status_code >= 300: raise TwilioRestException( response.status_code, - "https://api.twilio.com:8443", + "https://tls-test.twilio.com:443", "Failed to validate SSL certificate", ) diff --git a/twilio/rest/api/v2010/account/message/__init__.py b/twilio/rest/api/v2010/account/message/__init__.py index b15c6c6fbd..58ea4f32b8 100644 --- a/twilio/rest/api/v2010/account/message/__init__.py +++ b/twilio/rest/api/v2010/account/message/__init__.py @@ -86,7 +86,6 @@ class UpdateStatus(object): :ivar price_unit: The currency in which `price` is measured, in [ISO 4127](https://www.iso.org/iso/home/standards/currency_codes.htm) format (e.g. `usd`, `eur`, `jpy`). :ivar api_version: The API version used to process the Message :ivar subresource_uris: A list of related resources identified by their URIs relative to `https://api.twilio.com` - :ivar tags: A string containing a JSON map of key value pairs of tags to be recorded as metadata for the message. """ def __init__( @@ -126,7 +125,6 @@ def __init__( self.subresource_uris: Optional[Dict[str, object]] = payload.get( "subresource_uris" ) - self.tags: Optional[Dict[str, object]] = payload.get("tags") self._solution = { "account_sid": account_sid, @@ -495,7 +493,6 @@ def create( send_at: Union[datetime, object] = values.unset, send_as_mms: Union[bool, object] = values.unset, content_variables: Union[str, object] = values.unset, - tags: Union[str, object] = values.unset, risk_check: Union["MessageInstance.RiskCheck", object] = values.unset, from_: Union[str, object] = values.unset, messaging_service_sid: Union[str, object] = values.unset, @@ -523,7 +520,6 @@ def create( :param send_at: The time that Twilio will send the message. Must be in ISO 8601 format. :param send_as_mms: If set to `true`, Twilio delivers the message as a single MMS message, regardless of the presence of media. :param content_variables: For [Content Editor/API](https://www.twilio.com/docs/content) only: Key-value pairs of [Template variables](https://www.twilio.com/docs/content/using-variables-with-content-api) and their substitution values. `content_sid` parameter must also be provided. If values are not defined in the `content_variables` parameter, the [Template's default placeholder values](https://www.twilio.com/docs/content/content-api-resources#create-templates) are used. - :param tags: A string containing a JSON map of key value pairs of tags to be recorded as metadata for the message. The object may contain up to 10 tags. Keys and values can each be up to 128 characters in length. :param risk_check: :param from_: The sender's Twilio phone number (in [E.164](https://en.wikipedia.org/wiki/E.164) format), [alphanumeric sender ID](https://www.twilio.com/docs/sms/send-messages#use-an-alphanumeric-sender-id), [Wireless SIM](https://www.twilio.com/docs/iot/wireless/programmable-wireless-send-machine-machine-sms-commands), [short code](https://www.twilio.com/docs/sms/api/short-code), or [channel address](https://www.twilio.com/docs/messaging/channels) (e.g., `whatsapp:+15554449999`). The value of the `from` parameter must be a sender that is hosted within Twilio and belongs to the Account creating the Message. If you are using `messaging_service_sid`, this parameter can be empty (Twilio assigns a `from` value from the Messaging Service's Sender Pool) or you can provide a specific sender from your Sender Pool. :param messaging_service_sid: The SID of the [Messaging Service](https://www.twilio.com/docs/messaging/services) you want to associate with the Message. When this parameter is provided and the `from` parameter is omitted, Twilio selects the optimal sender from the Messaging Service's Sender Pool. You may also provide a `from` parameter if you want to use a specific Sender from the Sender Pool. @@ -552,7 +548,6 @@ def create( "SendAt": serialize.iso8601_datetime(send_at), "SendAsMms": send_as_mms, "ContentVariables": content_variables, - "Tags": tags, "RiskCheck": risk_check, "From": from_, "MessagingServiceSid": messaging_service_sid, @@ -595,7 +590,6 @@ async def create_async( send_at: Union[datetime, object] = values.unset, send_as_mms: Union[bool, object] = values.unset, content_variables: Union[str, object] = values.unset, - tags: Union[str, object] = values.unset, risk_check: Union["MessageInstance.RiskCheck", object] = values.unset, from_: Union[str, object] = values.unset, messaging_service_sid: Union[str, object] = values.unset, @@ -623,7 +617,6 @@ async def create_async( :param send_at: The time that Twilio will send the message. Must be in ISO 8601 format. :param send_as_mms: If set to `true`, Twilio delivers the message as a single MMS message, regardless of the presence of media. :param content_variables: For [Content Editor/API](https://www.twilio.com/docs/content) only: Key-value pairs of [Template variables](https://www.twilio.com/docs/content/using-variables-with-content-api) and their substitution values. `content_sid` parameter must also be provided. If values are not defined in the `content_variables` parameter, the [Template's default placeholder values](https://www.twilio.com/docs/content/content-api-resources#create-templates) are used. - :param tags: A string containing a JSON map of key value pairs of tags to be recorded as metadata for the message. The object may contain up to 10 tags. Keys and values can each be up to 128 characters in length. :param risk_check: :param from_: The sender's Twilio phone number (in [E.164](https://en.wikipedia.org/wiki/E.164) format), [alphanumeric sender ID](https://www.twilio.com/docs/sms/send-messages#use-an-alphanumeric-sender-id), [Wireless SIM](https://www.twilio.com/docs/iot/wireless/programmable-wireless-send-machine-machine-sms-commands), [short code](https://www.twilio.com/docs/sms/api/short-code), or [channel address](https://www.twilio.com/docs/messaging/channels) (e.g., `whatsapp:+15554449999`). The value of the `from` parameter must be a sender that is hosted within Twilio and belongs to the Account creating the Message. If you are using `messaging_service_sid`, this parameter can be empty (Twilio assigns a `from` value from the Messaging Service's Sender Pool) or you can provide a specific sender from your Sender Pool. :param messaging_service_sid: The SID of the [Messaging Service](https://www.twilio.com/docs/messaging/services) you want to associate with the Message. When this parameter is provided and the `from` parameter is omitted, Twilio selects the optimal sender from the Messaging Service's Sender Pool. You may also provide a `from` parameter if you want to use a specific Sender from the Sender Pool. @@ -652,7 +645,6 @@ async def create_async( "SendAt": serialize.iso8601_datetime(send_at), "SendAsMms": send_as_mms, "ContentVariables": content_variables, - "Tags": tags, "RiskCheck": risk_check, "From": from_, "MessagingServiceSid": messaging_service_sid, diff --git a/twilio/rest/trusthub/v1/__init__.py b/twilio/rest/trusthub/v1/__init__.py index 76692aee0d..10c3fd825b 100644 --- a/twilio/rest/trusthub/v1/__init__.py +++ b/twilio/rest/trusthub/v1/__init__.py @@ -15,6 +15,7 @@ from typing import Optional from twilio.base.version import Version from twilio.base.domain import Domain +from twilio.rest.trusthub.v1.compliance_inquiries import ComplianceInquiriesList from twilio.rest.trusthub.v1.customer_profiles import CustomerProfilesList from twilio.rest.trusthub.v1.end_user import EndUserList from twilio.rest.trusthub.v1.end_user_type import EndUserTypeList @@ -32,6 +33,7 @@ def __init__(self, domain: Domain): :param domain: The Twilio.trusthub domain """ super().__init__(domain, "v1") + self._compliance_inquiries: Optional[ComplianceInquiriesList] = None self._customer_profiles: Optional[CustomerProfilesList] = None self._end_users: Optional[EndUserList] = None self._end_user_types: Optional[EndUserTypeList] = None @@ -40,6 +42,12 @@ def __init__(self, domain: Domain): self._supporting_document_types: Optional[SupportingDocumentTypeList] = None self._trust_products: Optional[TrustProductsList] = None + @property + def compliance_inquiries(self) -> ComplianceInquiriesList: + if self._compliance_inquiries is None: + self._compliance_inquiries = ComplianceInquiriesList(self) + return self._compliance_inquiries + @property def customer_profiles(self) -> CustomerProfilesList: if self._customer_profiles is None: diff --git a/twilio/rest/trusthub/v1/compliance_inquiries.py b/twilio/rest/trusthub/v1/compliance_inquiries.py new file mode 100644 index 0000000000..c5c88d9155 --- /dev/null +++ b/twilio/rest/trusthub/v1/compliance_inquiries.py @@ -0,0 +1,260 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Twilio - Trusthub + This is the public Twilio REST API. + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + + +from typing import Any, Dict, Optional +from twilio.base import values +from twilio.base.instance_context import InstanceContext +from twilio.base.instance_resource import InstanceResource +from twilio.base.list_resource import ListResource +from twilio.base.version import Version + + +class ComplianceInquiriesInstance(InstanceResource): + + """ + :ivar inquiry_id: The unique ID used to start an embedded compliance registration session. + :ivar inquiry_session_token: The session token used to start an embedded compliance registration session. + :ivar customer_id: The CustomerID matching the Customer Profile that should be resumed or resubmitted for editing. + :ivar url: The URL of this resource. + """ + + def __init__( + self, + version: Version, + payload: Dict[str, Any], + customer_id: Optional[str] = None, + ): + super().__init__(version) + + self.inquiry_id: Optional[str] = payload.get("inquiry_id") + self.inquiry_session_token: Optional[str] = payload.get("inquiry_session_token") + self.customer_id: Optional[str] = payload.get("customer_id") + self.url: Optional[str] = payload.get("url") + + self._solution = { + "customer_id": customer_id or self.customer_id, + } + self._context: Optional[ComplianceInquiriesContext] = None + + @property + def _proxy(self) -> "ComplianceInquiriesContext": + """ + Generate an instance context for the instance, the context is capable of + performing various actions. All instance actions are proxied to the context + + :returns: ComplianceInquiriesContext for this ComplianceInquiriesInstance + """ + if self._context is None: + self._context = ComplianceInquiriesContext( + self._version, + customer_id=self._solution["customer_id"], + ) + return self._context + + def update(self, primary_profile_sid: str) -> "ComplianceInquiriesInstance": + """ + Update the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The updated ComplianceInquiriesInstance + """ + return self._proxy.update( + primary_profile_sid=primary_profile_sid, + ) + + async def update_async( + self, primary_profile_sid: str + ) -> "ComplianceInquiriesInstance": + """ + Asynchronous coroutine to update the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The updated ComplianceInquiriesInstance + """ + return await self._proxy.update_async( + primary_profile_sid=primary_profile_sid, + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format(context) + + +class ComplianceInquiriesContext(InstanceContext): + def __init__(self, version: Version, customer_id: str): + """ + Initialize the ComplianceInquiriesContext + + :param version: Version that contains the resource + :param customer_id: The unique CustomerId matching the Customer Profile/Compliance Inquiry that should be resumed or resubmitted. This value will have been returned by the initial Compliance Inquiry creation call. + """ + super().__init__(version) + + # Path Solution + self._solution = { + "customer_id": customer_id, + } + self._uri = "/ComplianceInquiries/Customers/{customer_id}/Initialize".format( + **self._solution + ) + + def update(self, primary_profile_sid: str) -> ComplianceInquiriesInstance: + """ + Update the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The updated ComplianceInquiriesInstance + """ + data = values.of( + { + "PrimaryProfileSid": primary_profile_sid, + } + ) + + payload = self._version.update( + method="POST", + uri=self._uri, + data=data, + ) + + return ComplianceInquiriesInstance( + self._version, payload, customer_id=self._solution["customer_id"] + ) + + async def update_async( + self, primary_profile_sid: str + ) -> ComplianceInquiriesInstance: + """ + Asynchronous coroutine to update the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The updated ComplianceInquiriesInstance + """ + data = values.of( + { + "PrimaryProfileSid": primary_profile_sid, + } + ) + + payload = await self._version.update_async( + method="POST", + uri=self._uri, + data=data, + ) + + return ComplianceInquiriesInstance( + self._version, payload, customer_id=self._solution["customer_id"] + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format(context) + + +class ComplianceInquiriesList(ListResource): + def __init__(self, version: Version): + """ + Initialize the ComplianceInquiriesList + + :param version: Version that contains the resource + + """ + super().__init__(version) + + self._uri = "/ComplianceInquiries/Customers/Initialize" + + def create(self, primary_profile_sid: str) -> ComplianceInquiriesInstance: + """ + Create the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The created ComplianceInquiriesInstance + """ + data = values.of( + { + "PrimaryProfileSid": primary_profile_sid, + } + ) + + payload = self._version.create( + method="POST", + uri=self._uri, + data=data, + ) + + return ComplianceInquiriesInstance(self._version, payload) + + async def create_async( + self, primary_profile_sid: str + ) -> ComplianceInquiriesInstance: + """ + Asynchronously create the ComplianceInquiriesInstance + + :param primary_profile_sid: The unique SID identifier of the Primary Customer Profile that should be used as a parent. Only necessary when creating a secondary Customer Profile. + + :returns: The created ComplianceInquiriesInstance + """ + data = values.of( + { + "PrimaryProfileSid": primary_profile_sid, + } + ) + + payload = await self._version.create_async( + method="POST", + uri=self._uri, + data=data, + ) + + return ComplianceInquiriesInstance(self._version, payload) + + def get(self, customer_id: str) -> ComplianceInquiriesContext: + """ + Constructs a ComplianceInquiriesContext + + :param customer_id: The unique CustomerId matching the Customer Profile/Compliance Inquiry that should be resumed or resubmitted. This value will have been returned by the initial Compliance Inquiry creation call. + """ + return ComplianceInquiriesContext(self._version, customer_id=customer_id) + + def __call__(self, customer_id: str) -> ComplianceInquiriesContext: + """ + Constructs a ComplianceInquiriesContext + + :param customer_id: The unique CustomerId matching the Customer Profile/Compliance Inquiry that should be resumed or resubmitted. This value will have been returned by the initial Compliance Inquiry creation call. + """ + return ComplianceInquiriesContext(self._version, customer_id=customer_id) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return ""