Skip to content

Commit

Permalink
feat: full error handling implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
filipporomani committed Nov 25, 2024
1 parent a558e95 commit f6cf72a
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 20 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Documentation is available in the [wiki](https://github.com/filipporomani/whatsa

### Key features:
- Modern interface using `async` and `await`
- Full support for Graph API error handling
- Optimized for high-load workflows using asyncronous programming
- Always up to date
- All of the WhatsApp chat UI features supported
Expand All @@ -44,6 +45,7 @@ Documentation is available in the [wiki](https://github.com/filipporomani/whatsa
### All features
- Event listener (incoming messages)
- Async ready
- Error handling
- Sending messages
- Sending messages from different numbers individually
- Marking messages as read
Expand Down
29 changes: 28 additions & 1 deletion tests/async_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
dest_phone_number = getenv("DEST_PHONE_NUMBER")

app = AsyncWhatsApp(token=token, phone_number_id=phone_number_id, update_check=False, logger=True, debug=True)
syncapp = WhatsApp(token=token, phone_number_id=phone_number_id, update_check=False, logger=True, debug=True)
loop = asyncio.get_event_loop()
msg = app.create_message(to=dest_phone_number, content="Hello world")

Expand Down Expand Up @@ -98,6 +99,28 @@ async def run_test():
dest_phone_number,
sender=1,
)
while not v.done():
await asyncio.sleep(1)
try: app.handle(v.result())
except Exception as error:
print(f"error: {error}")
print("sending wrong button")
v = await app.send_button(
{
"header": "Header Testing",

},
dest_phone_number,
sender=1,
)
while not v.done():
await asyncio.sleep(1)
try: app.handle(v.result())
except Exception as error:
print(f"error: {error}")
raise Exception("Error")


print(f"send_button: {v}")
print("sending image")
v = await app.send_image(
Expand Down Expand Up @@ -138,6 +161,9 @@ async def run_test():
f"send_template: {v}"
) # this returns error if the phone number is not a test phone number




await asyncio.sleep(60)

async def upload():
Expand All @@ -148,6 +174,7 @@ async def upload():
)
print(f"upload_media: {f}")
print(f)


asyncio.run(run_test())
asyncio.run(upload())
6 changes: 5 additions & 1 deletion whatsapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@
changed_field as async_changed_field,
)

from .errors import Handle


class WhatsApp:
def __init__(
Expand Down Expand Up @@ -359,7 +361,6 @@ def __init__(

# Check if the version is up to date
logging.getLogger(__name__).addHandler(logging.NullHandler())

self.l = phone_number_id

if isinstance(phone_number_id, dict):
Expand Down Expand Up @@ -539,6 +540,9 @@ async def hook(r: Request):
send_custom_json = async_send_custom_json
send_contacts = async_send_contacts
authorized = property(async_authorized)

def handle(self, data: dict):
return Handle(data)

def create_message(self, **kwargs) -> AsyncMessage:
"""
Expand Down
290 changes: 290 additions & 0 deletions whatsapp/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
from typing import Union


class Handler:
def __init__(self, error: dict):
self.error = error


class AuthException(Exception): # invoked by code 0
pass


class MethodException(AuthException): # invoked by code 3
pass


class ForbiddenException(AuthException): # invoked by codes 10, 200-299
pass


class ExpiredTokenException(AuthException): # invoked by code 190
pass


class ThrottlingException(Exception): # Category exception
pass


class RateLimitException(ThrottlingException): # invoked by code 4
pass


class UserRateLimitException(ThrottlingException): # invoked by code 80007
pass


class AppRateLimitException(ThrottlingException): # invoked by code 130429
pass


class SpamException(ThrottlingException): # invoked by code 131048
pass


class CoupleRateLimitException(ThrottlingException): # invoked by code 131056
pass


class RegistrationRateLimitException(ThrottlingException): # invoked by code 133016
pass


class IntegrityException(Exception): # Category exception
pass


class RulesViolationException(IntegrityException): # invoked by code 368
pass


class GeoRestrictionException(IntegrityException): # invoked by code 130497
pass


class BlockedAccountException(IntegrityException): # invoked by code 131031
pass


# Generic exceptions
class UnknownAPIException(Exception): # invoked by code 1
pass


class ServiceUnavailableException(Exception): # invoked by code 2
pass


class WrongPhoneNumberException(Exception): # invoked by code 33
pass


class InvalidParameterException(Exception): # invoked by code 100
pass


class ExperimentalNumberException(Exception): # invoked by code 130472
pass


class UnknownErrorException(Exception): # invoked by code 131000
pass


class AccessDeniedException(Exception): # invoked by code 131005
pass


class RequiredParameterMissingException(Exception): # invoked by code 131008
pass


class InvalidParameterTypeException(Exception): # invoked by code 131009
pass


class ServiceUnavailableException(Exception): # invoked by code 131016
pass


class SamePhoneNumberException(Exception): # invoked by code 131021
pass


class DeliveryFailureException(Exception): # invoked by code 131026
pass


class PaymentFailureException(Exception): # invoked by code 131042
pass


class PhoneRegistrationFailureException(Exception): # invoked by code 131045
pass


class ChatExpiredException(Exception): # invoked by code 131047
pass


class MetaDeliveryFailureException(Exception): # invoked by code 131049
pass


class UnsupportedMessageTypeException(Exception): # invoked by code 131051
pass


class MediaDownloadFailureException(Exception): # invoked by code 131052
pass


class MediaLoadFailureException(Exception): # invoked by code 131053
pass


class MaintenanceException(Exception): # invoked by code 131057
pass


class ParameterNumberMismatchException(Exception): # invoked by code 132000
pass


class ModelNotFoundException(Exception): # invoked by code 132001
pass


class TextTooLongException(Exception): # invoked by code 132005
pass


class CharacterFormatException(Exception): # invoked by code 132007
pass


class WrongParameterFormatException(Exception): # invoked by code 132012
pass


class PausedTemplateException(Exception): # invoked by code 132015
pass


class DisabledTemplateException(Exception): # invoked by code 132016
pass


class StreamBlockedException(Exception): # invoked by code 132068
pass


class StreamThrottlingException(Exception): # invoked by code 132069
pass


class FailedToRevertRegistrationException(Exception): # invoked by code 133000
pass


class ServerTemporaryUnavailableException(Exception): # invoked by code 133004
pass


class MFAPinIncorrectException(Exception): # invoked by code 133005
pass


class PhoneVerificationRequiredException(Exception): # invoked by code 133006
pass


class TooManyPinAttemptsException(Exception): # invoked by code 133008
pass


class TooFastPinAttemptsException(Exception): # invoked by code 133009
pass


class UnregisteredNumberException(Exception): # invoked by code 133010
pass


class RetryLaterException(Exception): # invoked by code 133015
pass


class GenericUserException(Exception): # invoked by code 135000
pass


pairings = {
0: AuthException,
1: UnknownAPIException,
2: ServiceUnavailableException,
3: MethodException,
4: RateLimitException,
10: ForbiddenException,
33: WrongPhoneNumberException,
100: InvalidParameterException,
200: ForbiddenException,
130429: AppRateLimitException,
130472: ExperimentalNumberException,
130497: GeoRestrictionException,
131000: UnknownErrorException,
131005: AccessDeniedException,
131008: RequiredParameterMissingException,
131009: InvalidParameterTypeException,
131016: ServiceUnavailableException,
131021: SamePhoneNumberException,
131026: DeliveryFailureException,
131042: PaymentFailureException,
131045: PhoneRegistrationFailureException,
131047: ChatExpiredException,
131048: SpamException,
131049: MetaDeliveryFailureException,
131051: UnsupportedMessageTypeException,
131052: MediaDownloadFailureException,
131053: MediaLoadFailureException,
131056: CoupleRateLimitException,
131057: MaintenanceException,
132000: ParameterNumberMismatchException,
132001: ModelNotFoundException,
132005: TextTooLongException,
132007: CharacterFormatException,
132012: WrongParameterFormatException,
132015: PausedTemplateException,
132016: DisabledTemplateException,
132068: StreamBlockedException,
132069: StreamThrottlingException,
133000: FailedToRevertRegistrationException,
133004: ServerTemporaryUnavailableException,
133005: MFAPinIncorrectException,
133006: PhoneVerificationRequiredException,
133008: TooManyPinAttemptsException,
133009: TooFastPinAttemptsException,
133010: UnregisteredNumberException,
133015: RetryLaterException,
80007: UserRateLimitException,
131031: BlockedAccountException,
131056: CoupleRateLimitException,
131057: MaintenanceException,
133016: RegistrationRateLimitException,
368: RulesViolationException,
190: ExpiredTokenException,
}


class Handle:
def __init__(self, data: dict) -> Union[Exception, None]:
try:
code = data["error"]["code"]
if code in pairings:
raise pairings[code]({"error": data["error"]["message"], "code": code})
else:
if code in range(200, 300):
raise ForbiddenException({"error": data["error"]["message"], "code": code})
raise Handler(Exception({"error": data["error"]["message"], "code": code}))
except KeyError:
return None
Loading

0 comments on commit f6cf72a

Please sign in to comment.