├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── examples ├── account.py ├── blacklist.py ├── contacts.py ├── hlr.py ├── mfa.py ├── mms.py ├── sender.py ├── short_url.py ├── sms.py └── vms.py ├── requirements.txt ├── runtests.py ├── setup.cfg ├── setup.py ├── smsapi ├── __init__.py ├── account │ ├── __init__.py │ ├── api.py │ └── models.py ├── api.py ├── auth.py ├── blacklist │ ├── __init__.py │ ├── api.py │ └── models.py ├── client.py ├── compat.py ├── contacts │ ├── __init__.py │ ├── api.py │ ├── exceptions.py │ └── models.py ├── endpoint.py ├── exception.py ├── hlr │ ├── __init__.py │ └── api.py ├── mfa │ ├── __init__.py │ ├── api.py │ └── model.py ├── mms │ ├── __init__.py │ └── api.py ├── models.py ├── sender │ ├── __init__.py │ ├── api.py │ └── models.py ├── short_url │ ├── __init__.py │ ├── api.py │ └── models.py ├── sms │ ├── __init__.py │ ├── api.py │ └── model.py ├── utils.py └── vms │ ├── __init__.py │ └── api.py └── tests ├── __init__.py └── unit ├── __init__.py ├── account ├── __init__.py ├── api_test.py └── fixtures │ ├── balance.json │ ├── user.json │ └── users_list.json ├── blacklist ├── __init__.py ├── api_test.py └── fixtures │ ├── collection.json │ ├── delete_all_phone_numbers.json │ ├── delete_phone_number.json │ └── phonenumber.json ├── client_test.py ├── contacts ├── __init__.py ├── api_test.py ├── fixtures.py └── fixtures │ ├── clean_trash.json │ ├── contact_is_in_group.json │ ├── count_contacts_in_trash.json │ ├── create_contact.json │ ├── create_custom_field.json │ ├── create_group.json │ ├── create_group_permission.json │ ├── delete_contact.json │ ├── delete_custom_field.json │ ├── delete_group.json │ ├── delete_user_group_permission.json │ ├── get_contact.json │ ├── get_group.json │ ├── list_contact_groups.json │ ├── list_contacts.json │ ├── list_custom_fields.json │ ├── list_group_permissions.json │ ├── list_groups.json │ ├── list_user_group_permissions.json │ ├── pin_contact_to_group.json │ ├── restore_contacts_in_trash.json │ ├── unpin_contact_from_group.json │ ├── unpin_contact_from_group_by_query.json │ ├── update_contact.json │ ├── update_custom_field.json │ ├── update_group.json │ └── update_user_group_permission.json ├── doubles.py ├── exceptions_test.py ├── fixtures.py ├── hlr ├── __init__.py ├── api_test.py └── fixtures │ └── check_number.json ├── mfa ├── __init__.py ├── api_test.py └── fixtures │ ├── send_mfa.json │ └── verify_mfa.json ├── mms ├── __init__.py ├── api_test.py └── fixtures │ ├── remove_not_exists_mms.json │ ├── remove_scheduled.json │ └── send.json ├── model_test.py ├── sender ├── __init__.py ├── api_test.py └── fixtures │ ├── check_sender_name.json │ ├── list.json │ └── success_response.json ├── short_url ├── __init__.py ├── api_test.py └── fixtures │ ├── get_clicks.json │ ├── get_clicks_by_mobile_device.json │ ├── list_short_urls.json │ ├── remove_short_url.json │ ├── short_url.json │ └── some_file ├── sms ├── __init__.py ├── api_test.py └── fixtures │ ├── detailed_response.json │ ├── remove_not_exists_sms.json │ ├── remove_scheduled.json │ ├── send.json │ ├── send_fast.json │ ├── send_flash.json │ ├── send_to_invalid_number.json │ └── send_to_many_recipients.json ├── utils_test.py └── vms ├── __init__.py ├── api_test.py └── fixtures ├── example.wav ├── remove_not_exists_vms.json ├── remove_scheduled.json └── send.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | 63 | ### JetBrains template 64 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 65 | 66 | *.iml 67 | 68 | ## Directory-based project format: 69 | .idea/ 70 | # if you remove the above rule, at least ignore the following: 71 | 72 | # User-specific stuff: 73 | # .idea/workspace.xml 74 | # .idea/tasks.xml 75 | # .idea/dictionaries 76 | 77 | # Sensitive or high-churn files: 78 | # .idea/dataSources.ids 79 | # .idea/dataSources.xml 80 | # .idea/sqlDataSources.xml 81 | # .idea/dynamic.xml 82 | # .idea/uiDesigner.xml 83 | 84 | # Gradle: 85 | # .idea/gradle.xml 86 | # .idea/libraries 87 | 88 | # Mongo Explorer plugin: 89 | # .idea/mongoSettings.xml 90 | 91 | ## File-based project format: 92 | *.ipr 93 | *.iws 94 | 95 | ## Plugin-specific files: 96 | 97 | # IntelliJ 98 | /out/ 99 | 100 | # mpeltonen/sbt-idea plugin 101 | .idea_modules/ 102 | 103 | # JIRA plugin 104 | atlassian-ide-plugin.xml 105 | 106 | # Crashlytics plugin (for Android Studio and IntelliJ) 107 | com_crashlytics_export_strings.xml 108 | crashlytics.properties 109 | crashlytics-build.properties 110 | 111 | /venv/ 112 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | language: python 4 | 5 | python: 6 | - "2.7" 7 | - "3.4" 8 | - "3.5" 9 | - "3.6" 10 | - "3.7" 11 | 12 | install: 13 | - pip install virtualenv --upgrade 14 | - make install 15 | 16 | script: 17 | - make tests 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 2.9.6 4 | - fix for python 3.12 - re.compile SyntaxWarning 5 | --- 6 | 7 | ## 2.9.5 8 | - fix examples 9 | - support "without_prefix" parameter in account API `user` method 10 | --- 11 | 12 | ## 2.9.4 13 | - fix module name typo 14 | --- 15 | 16 | ## 2.9.3 17 | - update API HTTP methods 18 | --- 19 | 20 | ## 2.9.2 21 | - fix: wrong parameter passed to DELETE scheduled SMS,MMS,VMS API endpoints 22 | --- 23 | 24 | ## 2.9.1 25 | - fix broken contacts API endpoints 26 | --- 27 | 28 | ## 2.9.0 29 | - introduce MFA module 30 | --- 31 | 32 | ## 2.8.0 33 | - remove push API 34 | --- 35 | 36 | ## 2.7.0 37 | - support MFA API 38 | - increase requests timeout to 30 seconds 39 | --- 40 | 41 | ## 2.6.0 42 | - Support for smsapi.bg 43 | ___ 44 | 45 | ## 2.5.0 46 | - Add support for optional `details` param in SMS API, so it makes fields like (`message`, `parts`, `length`) accessible through response model. 47 | ___ 48 | 49 | ## 2.4.5 50 | 51 | - make exceptions pickleable 52 | ___ 53 | 54 | ## 2.4.4 55 | 56 | - fix: accept 'domain' parameter from short_url API 57 | ___ 58 | 59 | ## 2.4.3 60 | 61 | - accept 'from' parameter in sms send method 62 | ___ 63 | 64 | ## 2.4.2 65 | 66 | - Change sms send http method to POST -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 SMSAPI 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | 4 | prune tests -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dist tests 2 | 3 | PYTHON = python3 4 | 5 | PROJECT := smsapi-client 6 | EGG_INFO := $(subst -,_,$(PROJECT)).egg-info 7 | 8 | 9 | venv: 10 | @${PYTHON} --version || (echo "Python is not installed."; exit 1) 11 | virtualenv --python=${PYTHON} venv 12 | 13 | 14 | install: venv 15 | . venv/bin/activate; pip install . 16 | 17 | 18 | tests: 19 | . venv/bin/activate; python runtests.py 20 | 21 | 22 | clean-venv: 23 | rm -rf venv 24 | 25 | 26 | clean: 27 | rm -rf dist build 28 | rm -rf $(EGG_INFO) 29 | 30 | find . -name '*.pyc' -delete 31 | find . -name '__pycache__' -delete 32 | 33 | 34 | clean-all: clean clean-venv 35 | 36 | 37 | dist: clean 38 | . venv/bin/activate; python setup.py sdist 39 | . venv/bin/activate; python setup.py bdist_wheel 40 | 41 | 42 | release: dist 43 | . venv/bin/activate; python -m twine upload dist/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smsapi-python 2 | 3 | [![Build Status](https://travis-ci.org/smsapi/smsapi-python-client.svg?branch=master)](https://travis-ci.org/smsapi/smsapi-python-client) 4 | [![PyPI](https://img.shields.io/pypi/v/smsapi-client.svg)](https://pypi.python.org/pypi/smsapi-client) 5 | 6 | Client for SMSAPI. 7 | 8 | ## COMPATIBILITY 9 | 10 | Compatible with Python 2.7+, 3.+. 11 | 12 | ## REQUIREMENTS 13 | 14 | requests 15 | 16 | ## INSTALLATION 17 | 18 | If You have pip installed: 19 | 20 | sudo pip install smsapi-client 21 | 22 | else You can install manually: 23 | 24 | git clone https://github.com/smsapi/smsapi-python-client.git 25 | 26 | cd smsapi-python 27 | 28 | python setup.py install 29 | 30 | ## Client instance 31 | 32 | If You are smsapi.pl customer You should import 33 | 34 | ```python 35 | from smsapi.client import SmsApiPlClient 36 | ``` 37 | 38 | else You need to use client for smsapi.com 39 | 40 | ```python 41 | from smsapi.client import SmsApiComClient 42 | ``` 43 | 44 | ## Credentials 45 | 46 | #### Access Token 47 | 48 | ```python 49 | from smsapi.client import SmsApiPlClient 50 | 51 | token = "XXXX" 52 | 53 | client = SmsApiPlClient(access_token=token) 54 | ``` 55 | 56 | ## Examples 57 | 58 | #### Send SMS 59 | 60 | ```python 61 | from smsapi.client import SmsApiPlClient 62 | 63 | token = "XXXX" 64 | 65 | client = SmsApiPlClient(access_token=token) 66 | 67 | send_results = client.sms.send(to="phone number", message="text message") 68 | 69 | for result in send_results: 70 | print(result.id, result.points, result.error) 71 | ``` 72 | 73 | - **You can find more examples in "examples" directory in project files.** 74 | 75 | ## Error handling 76 | 77 | ```python 78 | from smsapi.client import SmsApiPlClient 79 | from smsapi.exception import SmsApiException 80 | 81 | token = "XXXX" 82 | 83 | client = SmsApiPlClient(access_token=token) 84 | 85 | try: 86 | contact = client.sms.send(to="123123") 87 | except SmsApiException as e: 88 | print(e.message, e.code) 89 | ``` 90 | 91 | ## LICENSE 92 | 93 | [Apache 2.0 License](https://github.com/smsapi/smsapi-python-client/blob/master/LICENSE) 94 | -------------------------------------------------------------------------------- /examples/account.py: -------------------------------------------------------------------------------- 1 | import os 2 | from hashlib import md5 3 | 4 | from smsapi.client import SmsApiPlClient 5 | 6 | 7 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 8 | 9 | 10 | client = SmsApiPlClient(access_token=access_token) 11 | 12 | 13 | def get_account_balance(): 14 | r = client.account.balance() 15 | 16 | print(r.points, r.pro_count, r.eco_count, r.mms_count, r.vms_gsm_count, r.vms_land_count) 17 | 18 | 19 | def create_user(): 20 | r = client.account.create_user( 21 | name='some_user', 22 | password='d03444aa8b114fa3d1b8a3a2593c848a', 23 | api_password='67e8a30195188587421ba43fb8d9f20f', 24 | limit=1000, 25 | month_limit=100, 26 | senders=0, 27 | phonebook=0, 28 | active=1, 29 | info='some description', 30 | without_prefix=1) 31 | 32 | print(r.username, r.limit, r.month_limit, r.senders, r.phonebook, r.active, r.info) 33 | 34 | 35 | def get_user(): 36 | r = client.account.user(name='some_user', without_prefix=1) 37 | 38 | print(r.username, r.limit, r.month_limit, r.senders, r.phonebook, r.active, r.info) 39 | 40 | 41 | def update_user(): 42 | new_password = md5("new-password".encode("utf-8")).hexdigest() 43 | 44 | r = client.account.update_user(name='some_user', password=new_password, without_prefix=1) 45 | 46 | print(r.username, r.limit, r.month_limit, r.senders, r.phonebook, r.active, r.info) 47 | 48 | 49 | def list_users(): 50 | r = client.account.list_users() 51 | 52 | for u in r: 53 | print(u.username, u.limit, u.month_limit, u.senders, u.phonebook, u.active, u.info) 54 | -------------------------------------------------------------------------------- /examples/blacklist.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | 9 | client = SmsApiPlClient(access_token=access_token) 10 | 11 | 12 | def add_phone_number_to_blacklist(): 13 | r = client.blacklist.add_phone_number(phone_number="111222333", expire_at="2099-01-01T01:01:01Z") 14 | 15 | print(r.id, r.phone_number, r.expire_at, r.created_at) 16 | 17 | 18 | def get_phone_number_from_blacklist(): 19 | r = client.blacklist.add_phone_number(id="1") 20 | 21 | print(r.id, r.phone_number, r.expire_at, r.created_at) 22 | 23 | 24 | def list_phone_numbers_from_blacklist(): 25 | r = client.blacklist.list_phone_numbers() 26 | 27 | for n in r: 28 | print(n.id, n.phone_number, n.expire_at, n.created_at) 29 | 30 | 31 | def delete_phone_number_from_blacklist(): 32 | client.blacklist.delete_phone_number(id="1") 33 | 34 | 35 | def delete_all_phone_numbers_from_blacklist(): 36 | client.blacklist.delete_all_phone_numbers() 37 | -------------------------------------------------------------------------------- /examples/contacts.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | 9 | client = SmsApiPlClient(access_token=access_token) 10 | 11 | 12 | def create_contact(): 13 | c = client.contacts.create_contact( 14 | first_name='Jon', 15 | last_name='Doe', 16 | idx='id for Your use', 17 | phone_number=123123123, 18 | email='jondoe@somedomain.com', 19 | birthday_date='1970-01-01', 20 | gender='{male|female|undefined}', 21 | city='some_city', 22 | source='some_contact_source', 23 | description='Jon Doe') 24 | 25 | print( 26 | c.id, c.idx, c.first_name, c.last_name, c.phone_number, 27 | c.gender, c.city, c.email, c.source, c.description, 28 | c.birthday_date, c.date_created, c.date_updated) 29 | 30 | 31 | def update_contacts(): 32 | c = client.contacts.update_contact(contact_id=1, description='new_description') 33 | 34 | print(c.id, c.first_name, c.last_name, c.phone_number) 35 | 36 | 37 | def get_all_contacts(): 38 | l = client.contacts.list_contacts() 39 | 40 | for c in l: 41 | print(c.id, c.first_name, c.last_name, c.phone_number) 42 | 43 | 44 | def get_contacts(): 45 | c = client.contacts.get_contact(contact_id=1) 46 | 47 | print(c.id) 48 | 49 | 50 | def get_group(): 51 | g = client.contacts.get_group(group_id=1) 52 | 53 | print( 54 | g.id, g.name, g.contacts_count, g.date_created, 55 | g.date_updated, g.description, g.created_by, g.idx, g.permissions) 56 | 57 | 58 | def get_contact_groups(): 59 | l = client.contacts.list_contact_groups(contact_id=1) 60 | 61 | for g in l: 62 | print(g.id) 63 | 64 | 65 | def remove_contact(): 66 | client.contacts.delete_contact(contact_id=1) 67 | 68 | 69 | def create_group(): 70 | g = client.contacts.create_group(name='group_name', description='group_description') 71 | 72 | print(g.id, g.name, g.contacts_count, g.date_created) 73 | 74 | 75 | def get_all_groups(): 76 | l = client.contacts.list_groups() 77 | 78 | for g in l: 79 | print(g.id) 80 | 81 | 82 | def update_group(): 83 | g = client.contacts.update_group(group_id=1, name='new_name') 84 | 85 | print(g.id, g.name, g.contacts_count, g.date_created) 86 | 87 | 88 | def remove_group(): 89 | client.contacts.delete_group(group_id=1) 90 | 91 | 92 | def get_group_permissions(): 93 | p = client.contacts.list_group_permissions(group_id=1) 94 | 95 | print(p.username, p.group_id, p.write, p.read, p.send) 96 | 97 | 98 | def create_group_permissions(): 99 | client.contacts.create_group_permission(group_id=1, read = True, write = False, send = True) 100 | 101 | 102 | def get_user_group_permissions(): 103 | client.contacts.list_user_group_permissions(group_id=1, username='some_username') 104 | 105 | 106 | def remove_user_group_permissions(): 107 | client.contacts.delete_user_group_permission(group_id=1, username='some_username') 108 | 109 | 110 | def update_user_group_permissions(): 111 | client.contacts.update_user_group_permission(group_id=1, username='some_username', read=False) 112 | 113 | 114 | def remove_contacts_from_group(): 115 | client.contacts.unpin_contact_from_group(group_id=1, contact_id=1) 116 | 117 | 118 | def check_if_contact_is_in_group(): 119 | client.contacts.contact_is_in_group(group_id=1, contact_id=1) 120 | 121 | 122 | def list_custom_fields(): 123 | l = client.contacts.list_custom_fields() 124 | 125 | for f in l: 126 | print(f.id, f.name, f.type) 127 | 128 | 129 | def create_custom_field(): 130 | f = client.contacts.create_custom_field(name='some_field_name', type='{TEXT|DATE|EMAIL|NUMBER|PHONENUMBER|}') 131 | 132 | print(f.id, f.name, f.type) 133 | 134 | 135 | def update_custom_filed(): 136 | client.contacts.update_custom_field(field_id='1', name='new_field_name') 137 | 138 | 139 | def remove_custom_field(): 140 | client.contacts.delete_custom_field(field_id=1) 141 | -------------------------------------------------------------------------------- /examples/hlr.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | client = SmsApiPlClient(access_token=access_token) 9 | 10 | r = client.hlr.check_number(number='some-number') 11 | 12 | print(r.status, r.number, r.id, r.price) 13 | -------------------------------------------------------------------------------- /examples/mfa.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | client = SmsApiPlClient(access_token=access_token) 9 | 10 | client.mfa.send_mfa(phone_number='some-number', content='Your code: [%code%]', fast=0) 11 | 12 | client.mfa.verify_mfa(phone_number="", code="") 13 | -------------------------------------------------------------------------------- /examples/mms.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from smsapi.client import SmsApiPlClient 5 | 6 | 7 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 8 | 9 | 10 | client = SmsApiPlClient(access_token=access_token) 11 | 12 | 13 | def basic_send_mms(): 14 | client.mms.send(to='some-number', subject='some-subject', smil='SMIL formatted message') 15 | 16 | 17 | def send_mms_to_contacts_group(): 18 | client.mms.send_to_group(group='some-group', subject='some-subject', smil='SMIL formatted message') 19 | 20 | 21 | def remove_scheduled_mms(): 22 | client.mms.remove_scheduled(id='scheduled-mms-id') 23 | -------------------------------------------------------------------------------- /examples/sender.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | 9 | client = SmsApiPlClient(access_token=access_token) 10 | 11 | 12 | def add_sender_name(): 13 | r = client.sender.add(name='DarthVader') 14 | 15 | print(r.sender, r.status, r.default) 16 | 17 | 18 | def check_sender_name(): 19 | r = client.sender.check(name='DarthVader') 20 | 21 | print(r.sender, r.status, r.default) 22 | 23 | 24 | def remove_sender_name(): 25 | r = client.sender.remove(name='DarthVader') 26 | 27 | print(r.count) 28 | 29 | 30 | def make_sender_name_default(): 31 | r = client.sender.default(name='DarthVader') 32 | 33 | print(r.count) 34 | 35 | 36 | def list_sender_names(): 37 | r = client.sender.list() 38 | 39 | for sn in r: 40 | print(sn.sender, sn.status, sn.default) 41 | -------------------------------------------------------------------------------- /examples/short_url.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from smsapi.client import SmsApiPlClient 5 | 6 | 7 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 8 | 9 | 10 | client = SmsApiPlClient(access_token=access_token) 11 | 12 | 13 | def create_short_url(): 14 | r = client.shorturl.create_short_url(url='http://smsapi.pl/', name='smsapi') 15 | 16 | print( 17 | r.id, r.name, r.url, r.short_url, r.filename, r.type, 18 | r.expire, r.hits, r.hits_unique, r.description) 19 | 20 | 21 | def create_short_url_with_file(): 22 | r = client.shorturl.create_short_url(url='http://smsapi.com', file='path to file', name='smsapi') 23 | 24 | print(r.id, r.name, r.url) 25 | 26 | 27 | def get_clicks_statistics(): 28 | r = client.shorturl.get_clicks(date_from='2017-12-01', date_to='2017-12-10') 29 | 30 | print( 31 | r.idzdo_id, r.phone_number, r.date_hit, r.name, r.short_url, 32 | r.os, r.browser, r.device, r.suffix, r.message) 33 | 34 | 35 | def get_clicks_statistics_for_link(): 36 | response = client.shorturl.get_clicks(date_from='2017-12-01', date_to='2017-12-10', links=['1']) 37 | 38 | for r in response: 39 | print( 40 | r.idzdo_id, r.phone_number, r.date_hit, r.name, r.short_url, 41 | r.os, r.browser, r.device, r.suffix, r.message) 42 | -------------------------------------------------------------------------------- /examples/sms.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | client = SmsApiPlClient(access_token=access_token) 9 | 10 | # send single sms 11 | results = client.sms.send(to='some-number', message='some text message') 12 | 13 | for result in results: 14 | print(result.id, result.points, result.error) 15 | 16 | # send sms to many numbers 17 | results = client.sms.send(to=['123-123-123', '321-321-321'], message='some text message') 18 | 19 | for result in results: 20 | print(result.id, result.points, result.error) 21 | 22 | # send sms to contacts group 23 | client.sms.send_to_group(group='some-group', message='some text message') 24 | 25 | # send flash sms 26 | client.sms.send_flash(to='some-number', message='some text message') 27 | 28 | # send sms fast 29 | client.sms.send_fast(to='some-number', message='some text message') 30 | 31 | # remove_scheduled_sms 32 | client.sms.remove_scheduled(id='scheduled-sms-id') 33 | 34 | # send parametrized sms to many numbers 35 | client.sms.send(to=['number1', 'number2'], message='some text [%1%]', param1=['1', '2']) 36 | 37 | # send_sms_with_custom_sender_name 38 | client.sms.send(to='some-number', message='some text message', from_='your-custom-sender-name') 39 | -------------------------------------------------------------------------------- /examples/vms.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.client import SmsApiPlClient 4 | 5 | 6 | access_token = os.getenv('SMSAPI_ACCESS_TOKEN') 7 | 8 | 9 | client = SmsApiPlClient(access_token=access_token) 10 | 11 | 12 | def basic_send_vms(): 13 | client.vms.send(to='some-number', tts='some text message') 14 | 15 | 16 | def send_vms_from_file(): 17 | client.vms.send(to='some-number', file='/path/to/vms/file') 18 | 19 | 20 | def send_vms_to_contacts_group(): 21 | client.vms.send_to_group(group='some-group', tts='some text message') 22 | 23 | 24 | def send_vms_using_another_lector(): 25 | client.vms.send(to='some-number', tts='some text message', tts_lector='maja') 26 | 27 | 28 | def remove_scheduled_vms(): 29 | client.vms.remove_scheduled(id='scheduled-vms-id') 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.0.0 2 | twine -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | from tests import run_tests 7 | 8 | sys.path.insert(0, os.path.dirname(__file__)) 9 | 10 | run_tests() -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from smsapi import version, name 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.md') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name=name, 12 | version=version, 13 | description='SmsAPI client', 14 | author='SMSAPI', 15 | author_email='bok@smsapi.pl', 16 | url='https://github.com/smsapi/smsapi-python-client', 17 | packages=find_packages(exclude=["tests.*", "tests"]), 18 | include_package_data=True, 19 | install_requires=[ 20 | 'requests', 21 | ], 22 | classifiers=( 23 | 'Development Status :: 5 - Production/Stable', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: Apache Software License', 26 | 'Topic :: Software Development :: Libraries :: Python Modules', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3.4', 29 | 'Programming Language :: Python :: 3.5', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Programming Language :: Python :: 3.7', 32 | ), 33 | test_suite='tests.suite', 34 | long_description=long_description, 35 | long_description_content_type='text/markdown' 36 | ) 37 | -------------------------------------------------------------------------------- /smsapi/__init__.py: -------------------------------------------------------------------------------- 1 | name = 'smsapi-client' 2 | version = '2.9.6' 3 | 4 | lib_info = '%s/%s' % (name, version) 5 | -------------------------------------------------------------------------------- /smsapi/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/account/__init__.py -------------------------------------------------------------------------------- /smsapi/account/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.account.models import AccountBalanceResult, UserResult 2 | from smsapi.api import Api 3 | from smsapi.endpoint import bind_api_endpoint 4 | from smsapi.exception import EndpointException 5 | from smsapi.models import ResultCollection 6 | from smsapi.sms import response_format_param 7 | from smsapi.utils import update_dict 8 | 9 | accept_parameters = [ 10 | 'name', 11 | 'password', 12 | 'api_password', 13 | 'limit', 14 | 'month_limit', 15 | 'senders', 16 | 'phonebook', 17 | 'active', 18 | 'info', 19 | 'without_prefix' 20 | ] 21 | 22 | 23 | def parameters_transformer(mapping): 24 | def _parameters_transformer(_, parameters): 25 | for k, v in mapping.items(): 26 | parameters[v] = parameters.pop(k) 27 | 28 | parameters['pass'] = parameters.pop('password', None) 29 | parameters['pass_api'] = parameters.pop('api_password', None) 30 | 31 | return parameters 32 | 33 | return _parameters_transformer 34 | 35 | 36 | class Account(Api): 37 | 38 | path = 'user.do' 39 | 40 | balance = bind_api_endpoint( 41 | method='GET', 42 | path=path, 43 | mapping=AccountBalanceResult, 44 | force_parameters=update_dict(response_format_param, {'credits': 1, 'details': 1}), 45 | exception_class=EndpointException 46 | ) 47 | 48 | user = bind_api_endpoint( 49 | method='GET', 50 | path=path, 51 | mapping=UserResult, 52 | accept_parameters=['without_prefix', 'name'], 53 | force_parameters=response_format_param, 54 | exception_class=EndpointException, 55 | parameters_transformer=parameters_transformer({'name': 'get_user'}) 56 | ) 57 | 58 | create_user = bind_api_endpoint( 59 | method='POST', 60 | path=path, 61 | mapping=UserResult, 62 | accept_parameters=accept_parameters, 63 | force_parameters=response_format_param, 64 | exception_class=EndpointException, 65 | parameters_transformer=parameters_transformer({'name': 'add_user'}) 66 | ) 67 | 68 | update_user = bind_api_endpoint( 69 | method='PUT', 70 | path=path, 71 | mapping=UserResult, 72 | accept_parameters=accept_parameters, 73 | force_parameters=response_format_param, 74 | exception_class=EndpointException, 75 | parameters_transformer=parameters_transformer({'name': 'set_user'}) 76 | ) 77 | 78 | list_users = bind_api_endpoint( 79 | method='GET', 80 | path=path, 81 | mapping=(UserResult, ResultCollection), 82 | force_parameters=update_dict(response_format_param, {'list': 1}), 83 | exception_class=EndpointException 84 | ) -------------------------------------------------------------------------------- /smsapi/account/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import Model 4 | 5 | 6 | class AccountBalanceResult(Model): 7 | 8 | def __init__(self, points = None, pro_count = None, eco_count = None, 9 | mms_count = None, vms_gsm_count = None, vms_land_count = None): 10 | 11 | super(AccountBalanceResult, self).__init__() 12 | 13 | self.points = points 14 | self.pro_count = pro_count 15 | self.eco_count = eco_count 16 | self.mms_count = mms_count 17 | self.vms_gsm_count = vms_gsm_count 18 | self.vms_land_count = vms_land_count 19 | 20 | @classmethod 21 | def from_dict(cls, data, **kwargs): 22 | return cls( 23 | points=data.get('points'), 24 | pro_count=data.get('proCount'), 25 | eco_count=data.get('ecoCount'), 26 | mms_count=data.get('mmsCount'), 27 | vms_gsm_count=data.get('vmsGsmCount'), 28 | vms_land_count=data.get('vmsLandCount') 29 | ) 30 | 31 | 32 | class UserResult(Model): 33 | 34 | def __init__(self, username=None, limit=None, month_limit=None, 35 | senders=None, phonebook=None, active=None, info=None): 36 | 37 | super(UserResult, self).__init__() 38 | 39 | self.username = username 40 | self.limit = limit 41 | self.month_limit = month_limit 42 | self.senders = senders 43 | self.phonebook = phonebook 44 | self.active = active 45 | self.info = info 46 | -------------------------------------------------------------------------------- /smsapi/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Api(object): 5 | 6 | def __init__(self, api_client): 7 | super(Api, self).__init__() 8 | 9 | self.client = api_client 10 | -------------------------------------------------------------------------------- /smsapi/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from requests.auth import AuthBase 4 | 5 | 6 | class BearerAuth(AuthBase): 7 | 8 | def __init__(self, access_token): 9 | self.access_token = access_token 10 | 11 | def __eq__(self, other): 12 | return self.access_token == getattr(other, 'access_token', None) 13 | 14 | def __ne__(self, other): 15 | return not self == other 16 | 17 | def __call__(self, r): 18 | r.headers['Authorization'] = 'Bearer %s' % self.access_token 19 | return r 20 | -------------------------------------------------------------------------------- /smsapi/blacklist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/blacklist/__init__.py -------------------------------------------------------------------------------- /smsapi/blacklist/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.blacklist.models import BlacklistPhoneNumber 3 | from smsapi.endpoint import bind_api_endpoint 4 | from smsapi.exception import EndpointException 5 | from smsapi.models import ModelCollection 6 | 7 | list_params = ['q', 'offset', 'limit', 'orderBy'] 8 | 9 | accept_parameters = [ 10 | 'phone_number', 11 | 'expire_at' 12 | ] 13 | 14 | 15 | class Blacklist(Api): 16 | 17 | path = 'blacklist/phone_numbers' 18 | 19 | list_phone_numbers = bind_api_endpoint( 20 | method='GET', 21 | path=path, 22 | mapping=(BlacklistPhoneNumber, ModelCollection), 23 | accept_parameters=list_params, 24 | exception_class=EndpointException 25 | ) 26 | 27 | add_phone_number = bind_api_endpoint( 28 | method='POST', 29 | path=path, 30 | mapping=BlacklistPhoneNumber, 31 | accept_parameters=accept_parameters, 32 | exception_class=EndpointException 33 | ) 34 | 35 | delete_phone_number = bind_api_endpoint( 36 | method='DELETE', 37 | path="%s/{id}" % path, 38 | accept_parameters=['id'], 39 | exception_class=EndpointException 40 | ) 41 | 42 | delete_all_phone_numbers = bind_api_endpoint( 43 | method='DELETE', 44 | path=path, 45 | exception_class=EndpointException 46 | ) 47 | -------------------------------------------------------------------------------- /smsapi/blacklist/models.py: -------------------------------------------------------------------------------- 1 | from smsapi.models import Model 2 | from smsapi.utils import convert_iso8601_str_to_datetime 3 | 4 | 5 | class BlacklistPhoneNumber(Model): 6 | 7 | def __init__(self, **kwargs): 8 | super(BlacklistPhoneNumber, self).__init__() 9 | 10 | self.id = kwargs.get('id') 11 | self.phone_number = kwargs.get('phone_number') 12 | self.expire_at = kwargs.get('expire_at') 13 | self.created_at = kwargs.get('created_at') 14 | 15 | if self.expire_at: 16 | self.expire_at = convert_iso8601_str_to_datetime(self.expire_at) 17 | 18 | if self.created_at: 19 | self.created_at = convert_iso8601_str_to_datetime(self.created_at) 20 | -------------------------------------------------------------------------------- /smsapi/client.py: -------------------------------------------------------------------------------- 1 | from requests.auth import HTTPBasicAuth 2 | 3 | from smsapi.account.api import Account 4 | from smsapi.auth import BearerAuth 5 | from smsapi.blacklist.api import Blacklist 6 | from smsapi.contacts.api import Contacts 7 | from smsapi.exception import ClientException 8 | from smsapi.hlr.api import Hlr 9 | from smsapi.mfa.api import Mfa 10 | from smsapi.mms.api import Mms 11 | from smsapi.sender.api import Sender 12 | from smsapi.short_url.api import ShortUrl 13 | from smsapi.sms.api import Sms 14 | from smsapi.vms.api import Vms 15 | 16 | 17 | class Client(object): 18 | 19 | def __init__(self, domain, access_token=None, **kwargs): 20 | super(Client, self).__init__() 21 | 22 | self.domain = domain 23 | 24 | username = kwargs.get('username') 25 | password = kwargs.get('password') 26 | 27 | if not any((access_token, username, password)): 28 | raise ClientException('Credentials are required.') 29 | 30 | self.auth = BearerAuth(access_token) if access_token else HTTPBasicAuth(username, password) 31 | 32 | self.sms = Sms(self) 33 | self.mfa = Mfa(self) 34 | self.account = Account(self) 35 | self.contacts = Contacts(self) 36 | self.sender = Sender(self) 37 | self.shorturl = ShortUrl(self) 38 | self.hlr = Hlr(self) 39 | self.blacklist = Blacklist(self) 40 | 41 | 42 | class SmsApiPlClient(Client): 43 | 44 | def __init__(self, **kwargs): 45 | super(SmsApiPlClient, self).__init__('https://api.smsapi.pl/', **kwargs) 46 | 47 | self.mms = Mms(self) 48 | self.vms = Vms(self) 49 | 50 | 51 | class SmsApiComClient(Client): 52 | 53 | def __init__(self, **kwargs): 54 | super(SmsApiComClient, self).__init__('https://api.smsapi.com/', **kwargs) 55 | 56 | 57 | class SmsApiBgClient(Client): 58 | 59 | def __init__(self, **kwargs): 60 | super(SmsApiBgClient, self).__init__('https://api.smsapi.bg/', **kwargs) 61 | 62 | 63 | class SmsApiSwedenClient(Client): 64 | 65 | def __init__(self, **kwargs): 66 | super(SmsApiSwedenClient, self).__init__('https://api.smsapi.se/', **kwargs) 67 | -------------------------------------------------------------------------------- /smsapi/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | 5 | py_version = sys.version[:3] 6 | 7 | try: 8 | from urllib import quote 9 | except ImportError: 10 | from urllib.parse import quote 11 | 12 | try: 13 | from urlparse import urlparse 14 | except ImportError: 15 | from urllib.parse import urlparse 16 | -------------------------------------------------------------------------------- /smsapi/contacts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/contacts/__init__.py -------------------------------------------------------------------------------- /smsapi/contacts/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.contacts.exceptions import ContactsException 4 | from smsapi.models import ModelCollection 5 | from smsapi.contacts.models import ContactModel, GroupModel, GroupPermissionModel, CustomFieldModel 6 | 7 | from smsapi.api import Api 8 | from smsapi.endpoint import bind_api_endpoint 9 | from smsapi.models import HeaderDirectResult 10 | 11 | list_params = ['offset', 'limit', 'orderBy'] 12 | 13 | contact_params = ['first_name', 'last_name', 'phone_number', 'email', 14 | 'gender', 'birthday_date', 'description'] 15 | 16 | 17 | class Contacts(Api): 18 | 19 | list_contacts = bind_api_endpoint( 20 | method='GET', 21 | path='contacts', 22 | mapping=(ContactModel, ModelCollection), 23 | exception_class=ContactsException, 24 | accept_parameters=contact_params + list_params + ['q'] 25 | ) 26 | 27 | get_contact = bind_api_endpoint( 28 | method='GET', 29 | path='contacts/{contact_id}', 30 | mapping=ContactModel, 31 | exception_class=ContactsException, 32 | accept_parameters=['contact_id'] 33 | ) 34 | 35 | update_contact = bind_api_endpoint( 36 | method='PUT', 37 | path='contacts/{contact_id}', 38 | mapping=ContactModel, 39 | exception_class=ContactsException, 40 | accept_parameters=contact_params + ['contact_id'] 41 | ) 42 | 43 | create_contact = bind_api_endpoint( 44 | method='POST', 45 | path='contacts', 46 | mapping=ContactModel, 47 | exception_class=ContactsException, 48 | accept_parameters=contact_params 49 | ) 50 | 51 | delete_contact = bind_api_endpoint( 52 | method='DELETE', 53 | path='contacts/{contact_id}', 54 | exception_class=ContactsException, 55 | accept_parameters=['contact_id'] 56 | ) 57 | 58 | list_contact_groups = bind_api_endpoint( 59 | method='GET', 60 | path='contacts/{contact_id}/groups', 61 | mapping=(GroupModel, ModelCollection), 62 | exception_class=ContactsException, 63 | accept_parameters=['contact_id', 'group_id'] 64 | ) 65 | 66 | list_groups = bind_api_endpoint( 67 | method='GET', 68 | path='contacts/groups', 69 | mapping=(GroupModel, ModelCollection), 70 | accept_parameters=['group_id', 'name'], 71 | exception_class=ContactsException, 72 | force_parameters={'with': 'contacts_count'} 73 | ) 74 | 75 | create_group = bind_api_endpoint( 76 | method='POST', 77 | path='contacts/groups', 78 | mapping=GroupModel, 79 | exception_class=ContactsException, 80 | accept_parameters=['name', 'description', 'idx'] 81 | ) 82 | 83 | delete_group = bind_api_endpoint( 84 | method='DELETE', 85 | path='contacts/groups/{group_id}', 86 | exception_class=ContactsException, 87 | accept_parameters=['group_id'] 88 | ) 89 | 90 | get_group = bind_api_endpoint( 91 | method='GET', 92 | path='contacts/groups/{group_id}', 93 | mapping=GroupModel, 94 | accept_parameters=['group_id'], 95 | exception_class=ContactsException, 96 | force_parameters={'with': 'contacts_count'} 97 | ) 98 | 99 | update_group = bind_api_endpoint( 100 | method='PUT', 101 | path='contacts/groups/{group_id}', 102 | mapping=GroupModel, 103 | exception_class=ContactsException, 104 | accept_parameters=['group_id', 'name', 'description', 'idx'] 105 | ) 106 | 107 | list_group_permissions = bind_api_endpoint( 108 | method='GET', 109 | path='contacts/groups/{group_id}/permissions', 110 | mapping=GroupPermissionModel, 111 | exception_class=ContactsException, 112 | accept_parameters=['group_id'] 113 | ) 114 | 115 | create_group_permission = bind_api_endpoint( 116 | method='POST', 117 | path='contacts/groups/{group_id}/permissions', 118 | mapping=GroupPermissionModel, 119 | exception_class=ContactsException, 120 | accept_parameters=['group_id', 'username', 'read', 'write', 'send'] 121 | ) 122 | 123 | list_user_group_permissions = bind_api_endpoint( 124 | method='GET', 125 | path='contacts/groups/{group_id}/permissions/{username}', 126 | mapping=(GroupPermissionModel, ModelCollection), 127 | exception_class=ContactsException, 128 | accept_parameters=['group_id', 'username'] 129 | ) 130 | 131 | delete_user_group_permission = bind_api_endpoint( 132 | method='DELETE', 133 | path='contacts/groups/{group_id}/permissions/{username}', 134 | exception_class=ContactsException, 135 | accept_parameters=['group_id', 'username'] 136 | ) 137 | 138 | update_user_group_permission = bind_api_endpoint( 139 | method='PUT', 140 | path='contacts/groups/{group_id}/permissions/{username}', 141 | mapping=GroupPermissionModel, 142 | exception_class=ContactsException, 143 | accept_parameters=['group_id', 'username', 'read', 'write', 'send'] 144 | ) 145 | 146 | unpin_contact_from_group = bind_api_endpoint( 147 | method='DELETE', 148 | path='contacts/groups/{group_id}/members/{contact_id}', 149 | exception_class=ContactsException, 150 | accept_parameters=['group_id', 'contact_id'] 151 | ) 152 | 153 | contact_is_in_group = bind_api_endpoint( 154 | method='GET', 155 | path='contacts/groups/{group_id}/members/{contact_id}', 156 | mapping=ContactModel, 157 | exception_class=ContactsException, 158 | accept_parameters=['group_id', 'contact_id'] 159 | ) 160 | 161 | pin_contact_to_group = bind_api_endpoint( 162 | method='PUT', 163 | path='contacts/groups/{group_id}/members/{contact_id}', 164 | mapping=ContactModel, 165 | exception_class=ContactsException, 166 | accept_parameters=['group_id', 'contact_id'] 167 | ) 168 | 169 | list_custom_fields = bind_api_endpoint( 170 | method='GET', 171 | mapping=(CustomFieldModel, ModelCollection), 172 | exception_class=ContactsException, 173 | path='contacts/fields' 174 | ) 175 | 176 | create_custom_field = bind_api_endpoint( 177 | method='POST', 178 | path='contacts/fields', 179 | mapping=CustomFieldModel, 180 | exception_class=ContactsException, 181 | accept_parameters=['name', 'type'] 182 | ) 183 | 184 | delete_custom_field = bind_api_endpoint( 185 | method='DELETE', 186 | path='contacts/fields/{field_id}', 187 | exception_class=ContactsException, 188 | accept_parameters=['field_id'] 189 | ) 190 | 191 | update_custom_field = bind_api_endpoint( 192 | method='PUT', 193 | path='contacts/fields/{field_id}', 194 | mapping=CustomFieldModel, 195 | exception_class=ContactsException, 196 | accept_parameters=['field_id', 'name'] 197 | ) 198 | 199 | unpin_contact_from_group_by_query = bind_api_endpoint( 200 | method='DELETE', 201 | path='contacts/groups/{group_id}/members', 202 | exception_class=ContactsException, 203 | accept_parameters=contact_params[:-1] + ['group_id', 'q'] 204 | ) 205 | 206 | count_contacts_in_trash = bind_api_endpoint( 207 | method='HEAD', 208 | path='contacts/trash', 209 | mapping=HeaderDirectResult('X-Result-Count'), 210 | exception_class=ContactsException 211 | ) 212 | 213 | restore_contacts_in_trash = bind_api_endpoint( 214 | method='PUT', 215 | path='contacts/trash/restore', 216 | exception_class=ContactsException 217 | ) 218 | 219 | clean_trash = bind_api_endpoint( 220 | method='DELETE', 221 | path='contacts/trash', 222 | exception_class=ContactsException 223 | ) 224 | -------------------------------------------------------------------------------- /smsapi/contacts/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.exception import EndpointException 4 | 5 | 6 | class ContactsException(EndpointException): 7 | 8 | def __init__(self, message, code=None, type=None, errors=None): 9 | 10 | super(ContactsException, self).__init__(message) 11 | 12 | self.message = message 13 | self.code = code or '' 14 | self.type = type 15 | self.errors = errors 16 | 17 | @classmethod 18 | def from_dict(cls, data): 19 | """ 20 | Create EndpointException instance from dictionary. 21 | 22 | Args: 23 | data (dict) 24 | """ 25 | 26 | code = data.get('code') 27 | type = data.get('type') 28 | message = data.get('message') 29 | 30 | return cls(message, code, type) 31 | -------------------------------------------------------------------------------- /smsapi/contacts/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import Model 4 | from smsapi.utils import convert_iso8601_str_to_datetime, convert_date_str_to_date 5 | 6 | 7 | class ContactModel(Model): 8 | 9 | def __init__(self, **kwargs): 10 | super(ContactModel, self).__init__() 11 | 12 | self.id = kwargs.get('id') 13 | self.idx = kwargs.get('idx') 14 | self.first_name = kwargs.get('first_name') 15 | self.last_name = kwargs.get('last_name') 16 | self.phone_number = kwargs.get('phone_number') 17 | self.gender = kwargs.get('gender') 18 | self.city = kwargs.get('city') 19 | self.email = kwargs.get('email') 20 | self.source = kwargs.get('source') 21 | self.description = kwargs.get('description') 22 | self.birthday_date = kwargs.get('birthday_date') 23 | self.date_created = kwargs.get('date_created') 24 | self.date_updated = kwargs.get('date_updated') 25 | 26 | if self.birthday_date: 27 | self.birthday_date = convert_date_str_to_date(self.birthday_date) 28 | 29 | if self.date_created: 30 | self.date_created = convert_iso8601_str_to_datetime(self.date_created) 31 | 32 | if self.date_updated: 33 | self.date_updated = convert_iso8601_str_to_datetime(self.date_updated) 34 | 35 | 36 | class GroupModel(Model): 37 | 38 | def __init__(self, **kwargs): 39 | super(GroupModel, self).__init__() 40 | 41 | self.id = kwargs.get('id') 42 | self.name = kwargs.get('name') 43 | self.contacts_count = kwargs.get('contacts_count') 44 | self.date_created = kwargs.get('date_created') 45 | self.date_updated = kwargs.get('date_updated') 46 | self.description = kwargs.get('description') 47 | self.created_by = kwargs.get('created_by') 48 | self.idx = kwargs.get('idx') 49 | self.permissions = kwargs.get('permissions') 50 | 51 | @classmethod 52 | def from_dict(cls, data, **kwargs): 53 | model = super(GroupModel, cls).from_dict(data) 54 | 55 | permissions = data.get('permissions', []) 56 | model.permissions = [GroupPermissionModel.from_dict(p) for p in permissions] 57 | 58 | return model 59 | 60 | 61 | class CustomFieldModel(Model): 62 | 63 | def __init__(self, **kwargs): 64 | super(CustomFieldModel, self).__init__() 65 | 66 | self.id = kwargs.get('id') 67 | self.name = kwargs.get('name') 68 | self.type = kwargs.get('type') 69 | 70 | 71 | class GroupPermissionModel(Model): 72 | 73 | def __init__(self, **kwargs): 74 | super(GroupPermissionModel, self).__init__() 75 | 76 | self.username = kwargs.get('username') 77 | self.group_id = kwargs.get('group_id') 78 | self.write = kwargs.get('write') 79 | self.read = kwargs.get('read') 80 | self.send = kwargs.get('send') 81 | -------------------------------------------------------------------------------- /smsapi/endpoint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | import requests 5 | import uuid 6 | 7 | from . import lib_info 8 | from .compat import quote, py_version 9 | from .utils import convert_to_utf8_str, is_http_success, filter_dict 10 | from .exception import EndpointException 11 | 12 | 13 | def bind_api_endpoint(**config): 14 | class ApiEndpoint(object): 15 | 16 | method = config.get('method') 17 | 18 | path = config.get('path') 19 | 20 | response_mapping = config.get('mapping') 21 | 22 | accept_parameters = config.get('accept_parameters', []) 23 | 24 | force_parameters = config.get('force_parameters') 25 | 26 | parameters_transformer = config.get('parameters_transformer') 27 | 28 | exception_class = config.get('exception_class') 29 | 30 | def __init__(self, api, parameters): 31 | super(ApiEndpoint, self).__init__() 32 | 33 | self.api = api 34 | 35 | self.files = None 36 | 37 | self.parameters = parameters 38 | self.compiled_parameters = {} 39 | 40 | self.filter_parameters() 41 | self.compile_path() 42 | 43 | def filter_parameters(self): 44 | 45 | compiled_parameters = {} 46 | 47 | for key, val in self.parameters.items(): 48 | if key in self.accept_parameters: 49 | compiled_parameters[key] = convert_to_utf8_str(val) 50 | 51 | if self.force_parameters: 52 | compiled_parameters.update(self.force_parameters) 53 | 54 | if self.parameters_transformer: 55 | compiled_parameters = self.parameters_transformer(compiled_parameters) 56 | 57 | compiled_parameters = filter_dict(compiled_parameters) 58 | 59 | self.compiled_parameters = compiled_parameters 60 | 61 | def compile_path(self): 62 | 63 | placeholder_pattern = re.compile(r'{\w+}') 64 | 65 | for placeholder in placeholder_pattern.findall(self.path): 66 | name = placeholder.strip('{}') 67 | 68 | if name in self.compiled_parameters: 69 | param = quote(self.compiled_parameters.pop(name)) 70 | self.path = self.path.replace(placeholder, param) 71 | else: 72 | raise EndpointException("No parameter found for path variable '%s'" % name) 73 | 74 | def send_request(self): 75 | 76 | url = '%s%s' % (self.api.client.domain, self.path) 77 | 78 | headers = { 79 | 'User-Agent': '%s (Python%s)' % (lib_info, py_version), 80 | 'X-Request-Id': str(uuid.uuid4()) 81 | } 82 | 83 | kwargs = { 84 | 'auth': self.api.client.auth, 85 | 'headers': headers, 86 | 'files': self.files, 87 | 'timeout': self.parameters.get('timeout', 30) 88 | } 89 | 90 | if self.method == 'GET': 91 | kwargs.update({'params': self.compiled_parameters}) 92 | else: 93 | kwargs.update({'data': self.compiled_parameters}) 94 | 95 | raw_response = requests.request(self.method, url, **kwargs) 96 | 97 | return self.process_response(raw_response, url=url) 98 | 99 | def process_response(self, raw_response, url=None): 100 | 101 | if not is_http_success(raw_response.status_code): 102 | raise EndpointException(raw_response.text, raw_response.status_code, url=url, http_method=self.method) 103 | 104 | try: 105 | response = raw_response.json() 106 | except ValueError as e: 107 | response = {} 108 | 109 | if self.exception_class and isinstance(response, dict) and response.get('error'): 110 | raise self.exception_class.from_dict(response) 111 | 112 | if isinstance(self.response_mapping, tuple): 113 | model, type = self.response_mapping 114 | response = type.parse(response, model) 115 | elif self.response_mapping: 116 | response = self.response_mapping.from_dict(response, raw_response=raw_response) 117 | 118 | return response 119 | 120 | def add_file(self, file): 121 | self.files = {'file': file} 122 | 123 | def __call(api, **kwargs): 124 | endpoint = ApiEndpoint(api, kwargs) 125 | return endpoint.send_request() 126 | 127 | return __call 128 | -------------------------------------------------------------------------------- /smsapi/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import InvalidNumber 4 | 5 | 6 | class SmsApiException(Exception): 7 | 8 | def __init__(self, message, code=None, *args): 9 | super(SmsApiException, self).__init__(message, code, *args) 10 | 11 | self.message = message 12 | self.code = code 13 | 14 | @classmethod 15 | def from_dict(cls, data): 16 | message = data.get('message') 17 | error = data.get('error') 18 | 19 | return cls(message, error) 20 | 21 | def __eq__(self, other): 22 | return other and self.__dict__ == other.__dict__ 23 | 24 | def __hash__(self): 25 | return hash((self.message, self.code)) 26 | 27 | def __repr__(self): 28 | return "<%s %s>" % (self.__class__.__name__, self.__dict__) 29 | 30 | def __str__(self): 31 | return "<%s %s>" % (self.__class__.__name__, self.__dict__) 32 | 33 | 34 | class ClientException(SmsApiException): 35 | pass 36 | 37 | 38 | class EndpointException(SmsApiException): 39 | 40 | def __init__(self, message, code=None, *args, **kwargs): 41 | super(EndpointException, self).__init__(message, code, *args) 42 | 43 | self.url = kwargs.get('url') 44 | self.http_method = kwargs.get('http_method') 45 | 46 | 47 | class SendException(EndpointException): 48 | 49 | def __init__(self, message, code): 50 | super(SendException, self).__init__(message, code) 51 | 52 | self.invalid_numbers = [] 53 | 54 | @classmethod 55 | def from_dict(cls, response): 56 | 57 | message = response.get('message') 58 | error = response.get('error') 59 | 60 | e = cls(message, error) 61 | 62 | invalid_numbers = response.get('invalid_numbers') or [] 63 | 64 | for n in invalid_numbers: 65 | e.add_invalid_number(InvalidNumber.from_dict(n)) 66 | 67 | return e 68 | 69 | def add_invalid_number(self, invalid_number): 70 | self.invalid_numbers.append(invalid_number) 71 | -------------------------------------------------------------------------------- /smsapi/hlr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/hlr/__init__.py -------------------------------------------------------------------------------- /smsapi/hlr/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.endpoint import bind_api_endpoint 3 | from smsapi.exception import EndpointException 4 | from smsapi.models import Model 5 | from smsapi.sms import response_format_param 6 | from smsapi.utils import join_params 7 | 8 | 9 | def parameters_transformer(_, parameters): 10 | join_params(parameters, ['idx'], '|') 11 | return parameters 12 | 13 | 14 | class HlrResult(Model): 15 | 16 | def __init__(self, status=None, number=None, id=None, price=None): 17 | super(HlrResult, self).__init__() 18 | 19 | self.status = status 20 | self.number = number 21 | self.id = id 22 | self.price = price 23 | 24 | 25 | class Hlr(Api): 26 | 27 | path = 'hlr.do' 28 | 29 | check_number = bind_api_endpoint( 30 | method='POST', 31 | path=path, 32 | mapping=HlrResult, 33 | accept_parameters=['number', 'idx'], 34 | force_parameters=response_format_param, 35 | exception_class=EndpointException, 36 | parameters_transformer=parameters_transformer 37 | ) 38 | -------------------------------------------------------------------------------- /smsapi/mfa/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/mfa/__init__.py -------------------------------------------------------------------------------- /smsapi/mfa/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.endpoint import bind_api_endpoint 3 | from smsapi.exception import EndpointException, SendException 4 | from smsapi.models import SendResult 5 | from smsapi.sms import response_format_param 6 | from smsapi.mfa.model import SmsMFASendResult, SmsMFAVerifyResult 7 | 8 | mfa_parameters = [ 9 | 'phone_number', 10 | 'from', 11 | 'from_', 12 | 'content', 13 | 'fast' 14 | ] 15 | 16 | mfa_verify_parameters = [ 17 | 'phone_number', 18 | 'code' 19 | ] 20 | 21 | 22 | def parameters_transformer(_, parameters): 23 | if 'from_' in parameters: 24 | parameters['from'] = parameters.pop('from_') 25 | 26 | return parameters 27 | 28 | 29 | class Mfa(Api): 30 | 31 | send_mfa = bind_api_endpoint( 32 | method='POST', 33 | path='mfa/codes', 34 | mapping=(SendResult, SmsMFASendResult), 35 | accept_parameters=mfa_parameters, 36 | force_parameters=response_format_param, 37 | exception_class=SendException, 38 | parameters_transformer=parameters_transformer 39 | ) 40 | 41 | verify_mfa = bind_api_endpoint( 42 | method='POST', 43 | path='mfa/codes/verifications', 44 | mapping=(SendResult, SmsMFAVerifyResult), 45 | accept_parameters=mfa_verify_parameters, 46 | force_parameters=response_format_param, 47 | exception_class=EndpointException, 48 | parameters_transformer=parameters_transformer 49 | ) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /smsapi/mfa/model.py: -------------------------------------------------------------------------------- 1 | from smsapi.models import ResultCollection 2 | 3 | 4 | class SmsMFASendResult(ResultCollection): 5 | 6 | def __init__(self, count, results, code, phone_number, from_): 7 | super(SmsMFASendResult, self).__init__(count, results) 8 | 9 | self.code = code 10 | self.phone_number = phone_number 11 | self.from_ = from_ 12 | 13 | @classmethod 14 | def parse(cls, json_response, model): 15 | code = json_response.get('code') 16 | phone_number = json_response.get('phone_number') 17 | from_ = json_response.get('from') 18 | 19 | sms_list = [json_response] 20 | 21 | collection = [] 22 | 23 | for sms in sms_list: 24 | m = model.from_dict(sms) 25 | collection.append(m) 26 | 27 | return cls(1, collection, code=code, phone_number=phone_number, from_=from_) 28 | 29 | 30 | class SmsMFAVerifyResult(ResultCollection): 31 | 32 | def __init__(self, count, results): 33 | super(SmsMFAVerifyResult, self).__init__(count, results) 34 | 35 | @classmethod 36 | def parse(cls, json_response, model): 37 | return cls(0, []) 38 | -------------------------------------------------------------------------------- /smsapi/mms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/mms/__init__.py -------------------------------------------------------------------------------- /smsapi/mms/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.endpoint import bind_api_endpoint 3 | from smsapi.exception import EndpointException, SendException 4 | from smsapi.models import ResultCollection, SendResult, RemoveMessageResult 5 | from smsapi.sms import response_format_param 6 | from smsapi.utils import join_params 7 | 8 | accept_parameters = [ 9 | 'subject', 10 | 'smil', 11 | 'date', 12 | 'date_validate', 13 | 'idx', 14 | 'check_idx', 15 | 'notify_url', 16 | 'test' 17 | ] 18 | 19 | 20 | def parameters_transformer(_, parameters): 21 | join_params(parameters, ['idx'], '|') 22 | 23 | return parameters 24 | 25 | 26 | def delete_sms_params_transformer(_, parameters): 27 | join_params(parameters, ['sch_del']) 28 | 29 | if 'id' in parameters: 30 | parameters['sch_del'] = parameters.pop('id') 31 | 32 | return parameters 33 | 34 | 35 | class Mms(Api): 36 | 37 | path = 'mms.do' 38 | 39 | send = bind_api_endpoint( 40 | method='POST', 41 | path=path, 42 | mapping=(SendResult, ResultCollection), 43 | accept_parameters=accept_parameters + ['to'], 44 | force_parameters=response_format_param, 45 | exception_class=SendException, 46 | parameters_transformer=parameters_transformer 47 | ) 48 | 49 | send_to_group = bind_api_endpoint( 50 | method='POST', 51 | path=path, 52 | mapping=(SendResult, ResultCollection), 53 | accept_parameters=accept_parameters + ['group'], 54 | force_parameters=response_format_param, 55 | exception_class=SendException, 56 | parameters_transformer=parameters_transformer 57 | ) 58 | 59 | remove_scheduled = bind_api_endpoint( 60 | method='POST', 61 | path=path, 62 | mapping=(RemoveMessageResult, ResultCollection), 63 | accept_parameters=['id'], 64 | force_parameters=response_format_param, 65 | exception_class=EndpointException, 66 | parameters_transformer=delete_sms_params_transformer 67 | ) 68 | -------------------------------------------------------------------------------- /smsapi/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Model(object): 5 | 6 | @classmethod 7 | def from_dict(cls, data, **kwargs): 8 | return cls(**data) 9 | 10 | def __repr__(self): 11 | return "<%s %s>" % (self.__class__.__name__, self.__dict__) 12 | 13 | def __eq__(self, other): 14 | return other and self.__dict__ == other.__dict__ 15 | 16 | def __ne__(self, other): 17 | return not self.__eq__(other) 18 | 19 | 20 | class ModelCollection(object): 21 | 22 | def __init__(self, size, collection): 23 | super(ModelCollection, self).__init__() 24 | 25 | self.size = size 26 | 27 | self.collection = collection 28 | 29 | self._current = None 30 | self._index = 0 31 | 32 | @classmethod 33 | def parse(cls, response, model): 34 | size = response.get('size') 35 | collection = response.get('collection', []) 36 | 37 | c = [model.from_dict(d) for d in collection] 38 | 39 | return cls(size, c) 40 | 41 | def __iter__(self): 42 | return self 43 | 44 | def __next__(self): 45 | return self.next() 46 | 47 | def next(self): 48 | try: 49 | self._current = self.collection[self._index] 50 | self._index += 1 51 | except IndexError: 52 | raise StopIteration 53 | 54 | return self._current 55 | 56 | def __repr__(self): 57 | return "<%s [%s]> %s" % (self.__class__.__name__, self.size, self.collection) 58 | 59 | def __eq__(self, other): 60 | return other and self.__dict__ == other.__dict__ 61 | 62 | 63 | class ResultCollection(object): 64 | 65 | def __init__(self, count, results): 66 | super(ResultCollection, self).__init__() 67 | 68 | self.count = count 69 | self.results = results 70 | 71 | self._current = None 72 | self._index = 0 73 | 74 | @classmethod 75 | def parse(cls, content, model): 76 | collection = [] 77 | 78 | if isinstance(content, dict): 79 | count = content.get('count') 80 | content = content.get('list', []) 81 | else: 82 | count = len(content) 83 | 84 | for data in content: 85 | m = model.from_dict(data) 86 | collection.append(m) 87 | 88 | return cls(count, collection) 89 | 90 | def __iter__(self): 91 | return self 92 | 93 | def __next__(self): 94 | return self.next() 95 | 96 | def next(self): 97 | try: 98 | self._current = self.results[self._index] 99 | self._index += 1 100 | except IndexError: 101 | raise StopIteration 102 | 103 | return self._current 104 | 105 | def __repr__(self): 106 | return "<%s [%s]> %s" % (self.__class__.__name__, self.count, self.results) 107 | 108 | def __eq__(self, other): 109 | return other and self.__dict__ == other.__dict__ 110 | 111 | 112 | class SendResult(Model): 113 | 114 | def __init__(self, **kwargs): 115 | 116 | super(SendResult, self).__init__() 117 | 118 | self.id = kwargs.get("id") 119 | self.points = kwargs.get("points") 120 | self.number = kwargs.get("number") 121 | self.date_sent = kwargs.get("date_sent") 122 | self.submitted_number = kwargs.get("submitted_number") 123 | self.status = kwargs.get("status") 124 | self.idx = kwargs.get("idx") 125 | self.error = kwargs.get("error") 126 | self.parts = kwargs.get("parts") 127 | 128 | 129 | class RemoveMessageResult(Model): 130 | 131 | def __init__(self, id=None): 132 | super(RemoveMessageResult, self).__init__() 133 | 134 | self.id = id 135 | 136 | 137 | class InvalidNumber(object): 138 | 139 | def __init__(self, number, submitted_number, reason): 140 | super(InvalidNumber, self).__init__() 141 | 142 | self.number = number 143 | self.submitted_number = submitted_number 144 | self.reason = reason 145 | 146 | @classmethod 147 | def from_dict(cls, data): 148 | return cls(data.get('number'), data.get('submitted_number'), data.get('message')) 149 | 150 | def __eq__(self, other): 151 | return other and self.__dict__ == other.__dict__ 152 | 153 | 154 | class HeaderDirectResult(object): 155 | 156 | def __init__(self, header): 157 | super(HeaderDirectResult, self).__init__() 158 | 159 | self.header = header 160 | 161 | def from_dict(self, _, **kwargs): 162 | r = kwargs.get('raw_response') 163 | return r.headers.get(self.header) 164 | -------------------------------------------------------------------------------- /smsapi/sender/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/sender/__init__.py -------------------------------------------------------------------------------- /smsapi/sender/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.endpoint import bind_api_endpoint 3 | from smsapi.exception import EndpointException 4 | from smsapi.models import ResultCollection 5 | from smsapi.sms import response_format_param 6 | from smsapi.utils import update_dict 7 | from smsapi.sender.models import SenderNameResult, SenderNameSuccessResult 8 | 9 | 10 | def parameters_transformer(mapped_param): 11 | def _parameters_transformer(_, parameters): 12 | parameters[mapped_param] = parameters.pop('name', None) 13 | return parameters 14 | return _parameters_transformer 15 | 16 | 17 | class Sender(Api): 18 | 19 | path = 'sender.do' 20 | 21 | add = bind_api_endpoint( 22 | method='POST', 23 | path=path, 24 | mapping=SenderNameSuccessResult, 25 | accept_parameters=['name'], 26 | force_parameters=response_format_param, 27 | exception_class=EndpointException, 28 | parameters_transformer=parameters_transformer('add') 29 | ) 30 | 31 | check = bind_api_endpoint( 32 | method='POST', 33 | path=path, 34 | mapping=SenderNameResult, 35 | accept_parameters=['name'], 36 | force_parameters=response_format_param, 37 | exception_class=EndpointException, 38 | parameters_transformer=parameters_transformer('status') 39 | ) 40 | 41 | remove = bind_api_endpoint( 42 | method='POST', 43 | path=path, 44 | mapping=SenderNameSuccessResult, 45 | accept_parameters=['name'], 46 | force_parameters=response_format_param, 47 | exception_class=EndpointException, 48 | parameters_transformer=parameters_transformer('delete') 49 | ) 50 | 51 | list = bind_api_endpoint( 52 | method='GET', 53 | path=path, 54 | mapping=(SenderNameResult, ResultCollection), 55 | force_parameters=update_dict(response_format_param, {'list': True}), 56 | exception_class=EndpointException 57 | ) 58 | 59 | default = bind_api_endpoint( 60 | method='POST', 61 | path=path, 62 | mapping=SenderNameSuccessResult, 63 | accept_parameters=['name'], 64 | force_parameters=response_format_param, 65 | exception_class=EndpointException, 66 | parameters_transformer=parameters_transformer('default') 67 | ) 68 | -------------------------------------------------------------------------------- /smsapi/sender/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import Model 4 | 5 | 6 | class SenderNameResult(Model): 7 | 8 | def __init__(self, sender=None, status=None, default=None, **kwargs): 9 | super(SenderNameResult, self).__init__() 10 | 11 | self.sender = sender or kwargs.get('name') 12 | self.status = status 13 | self.default = default 14 | 15 | 16 | class SenderNameSuccessResult(Model): 17 | 18 | def __init__(self, count=None): 19 | super(SenderNameSuccessResult, self).__init__() 20 | 21 | self.count = count 22 | -------------------------------------------------------------------------------- /smsapi/short_url/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/short_url/__init__.py -------------------------------------------------------------------------------- /smsapi/short_url/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from smsapi.api import Api 6 | from smsapi.endpoint import bind_api_endpoint 7 | from smsapi.exception import EndpointException 8 | from smsapi.models import ModelCollection 9 | from smsapi.short_url.models import ShortUrlClick, ShortUrlClickByMobileDevice, ShortUrl as ShortUrlModel 10 | 11 | clicks_accept_parameters = [ 12 | 'date_from', 13 | 'date_to', 14 | 'links' 15 | ] 16 | 17 | short_url_parameters = [ 18 | 'url', 19 | 'file', 20 | 'name', 21 | 'expire_time', 22 | 'expire_unit', 23 | 'description' 24 | ] 25 | 26 | short_url_deprecated_parameters = [ 27 | 'expire' 28 | ] 29 | 30 | def parameters_transformer(api_endpoint, parameters): 31 | if 'file' in parameters and os.path.isfile(parameters.get('file')): 32 | api_endpoint.add_file(open(parameters.pop('file'), 'rb')) 33 | return parameters 34 | 35 | 36 | def clicks_parameters_transformer(_, parameters): 37 | if 'links' in parameters: 38 | parameters['links[]'] = parameters.pop('links') 39 | return parameters 40 | 41 | 42 | class ShortUrl(Api): 43 | 44 | get_clicks = bind_api_endpoint( 45 | method='GET', 46 | path='short_url/clicks', 47 | mapping=(ShortUrlClick, ModelCollection), 48 | exception_class=EndpointException, 49 | accept_parameters=clicks_accept_parameters, 50 | parameters_transformer=clicks_parameters_transformer 51 | ) 52 | 53 | get_clicks_by_mobile_device = bind_api_endpoint( 54 | method='GET', 55 | path='short_url/clicks_by_mobile_device', 56 | mapping=(ShortUrlClickByMobileDevice, ModelCollection), 57 | exception_class=EndpointException, 58 | accept_parameters=clicks_accept_parameters, 59 | parameters_transformer=clicks_parameters_transformer 60 | ) 61 | 62 | list_short_urls = bind_api_endpoint( 63 | method='GET', 64 | path='short_url/links', 65 | mapping=(ShortUrlModel, ModelCollection), 66 | exception_class=EndpointException 67 | ) 68 | 69 | create_short_url = bind_api_endpoint( 70 | method='POST', 71 | path='short_url/links', 72 | mapping=ShortUrlModel, 73 | exception_class=EndpointException, 74 | accept_parameters=short_url_parameters + short_url_deprecated_parameters, 75 | parameters_transformer=parameters_transformer 76 | ) 77 | 78 | get_short_url = bind_api_endpoint( 79 | method='GET', 80 | path='short_url/links/{id}', 81 | mapping=ShortUrlModel, 82 | exception_class=EndpointException, 83 | accept_parameters=['id'] 84 | ) 85 | 86 | update_short_url = bind_api_endpoint( 87 | method='PUT', 88 | path='short_url/links/{id}', 89 | mapping=ShortUrlModel, 90 | exception_class=EndpointException, 91 | accept_parameters=['id'] 92 | ) 93 | 94 | remove_short_url = bind_api_endpoint( 95 | method='DELETE', 96 | path='short_url/links/{id}', 97 | exception_class=EndpointException, 98 | accept_parameters=['id'] 99 | ) 100 | -------------------------------------------------------------------------------- /smsapi/short_url/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import Model 4 | 5 | 6 | class ShortUrlClickByMobileDevice(Model): 7 | 8 | def __init__(self, link_id=None, android=None, ios=None, wp=None, other=None, sum=None): 9 | super(ShortUrlClickByMobileDevice, self).__init__() 10 | 11 | self.link_id = link_id 12 | self.android = android 13 | self.ios = ios 14 | self.wp = wp 15 | self.other = other 16 | self.sum = sum 17 | 18 | @classmethod 19 | def from_dict(cls, data, **kwargs): 20 | data.update(data.pop('clicks', {})) 21 | return super(ShortUrlClickByMobileDevice, cls).from_dict(data, **kwargs) 22 | 23 | 24 | class ShortUrlClick(Model): 25 | 26 | def __init__(self, idzdo_id=None, phone_number=None, date_hit=None, name=None, 27 | short_url=None, os=None, browser=None, device=None, suffix=None, message=None): 28 | 29 | super(ShortUrlClick, self).__init__() 30 | 31 | self.idzdo_id = idzdo_id 32 | self.phone_number = phone_number 33 | self.date_hit = date_hit 34 | self.name = name 35 | self.short_url = short_url 36 | self.os = os 37 | self.browser = browser 38 | self.device = device 39 | self.suffix = suffix 40 | self.message = message 41 | 42 | @classmethod 43 | def from_dict(cls, data, **kwargs): 44 | data['idzdo_id'] = data.pop('idz_do_id', None) 45 | data['message'] = ShortUrlClickMessage.from_dict(data.pop('message', {})) 46 | return super(ShortUrlClick, cls).from_dict(data, **kwargs) 47 | 48 | 49 | class ShortUrlClickMessage(Model): 50 | 51 | def __init__(self, id=None, idx=None, recipient=None): 52 | super(ShortUrlClickMessage, self).__init__() 53 | 54 | self.id = id 55 | self.idx = idx 56 | self.recipient = recipient 57 | 58 | 59 | class ShortUrl(Model): 60 | 61 | def __init__(self, id=None, name=None, url=None, short_url=None, filename=None, 62 | type=None, expire=None, hits=None, hits_unique=None, description=None, 63 | domain=None): 64 | 65 | super(ShortUrl, self).__init__() 66 | 67 | self.id = id 68 | self.name = name 69 | self.url = url 70 | self.short_url = short_url 71 | self.filename = filename 72 | self.type = type 73 | self.expire = expire 74 | self.hits = hits 75 | self.hits_unique = hits_unique 76 | self.description = description 77 | self.domain = domain 78 | -------------------------------------------------------------------------------- /smsapi/sms/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | default_response = 'json' 4 | 5 | response_format_param = {'format': default_response} -------------------------------------------------------------------------------- /smsapi/sms/api.py: -------------------------------------------------------------------------------- 1 | from smsapi.api import Api 2 | from smsapi.endpoint import bind_api_endpoint 3 | from smsapi.exception import EndpointException, SendException 4 | from smsapi.models import ResultCollection, SendResult, RemoveMessageResult 5 | from smsapi.sms import response_format_param 6 | from smsapi.sms.model import SmsSendResult 7 | from smsapi.utils import join_params 8 | 9 | sms_parameters = [ 10 | 'message', 11 | 'from_', 12 | 'from', 13 | 'encoding', 14 | 'details', 15 | 'template', 16 | 'date', 17 | 'datacoding', 18 | 'udh', 19 | 'skip_foreign', 20 | 'allow_duplicates', 21 | 'idx', 22 | 'check_idx', 23 | 'nounicode', 24 | 'normalize', 25 | 'partner_id', 26 | 'max_parts', 27 | 'expiration_date', 28 | 'discount_group', 29 | 'notify_url', 30 | 'param1', 31 | 'param2', 32 | 'param3', 33 | 'param4', 34 | 'test' 35 | ] 36 | 37 | fast_force_params = {'fast': 1} 38 | flash_force_params = {'flash': 1} 39 | 40 | fast_force_params.update(response_format_param) 41 | flash_force_params.update(response_format_param) 42 | 43 | 44 | def parameters_transformer(_, parameters): 45 | 46 | join_params(parameters, ['to', 'id']) 47 | join_params(parameters, ['param1', 'param2', 'param3', 'param4', 'idx'], '|') 48 | 49 | if 'from_' in parameters: 50 | parameters['from'] = parameters.pop('from_') 51 | 52 | return parameters 53 | 54 | 55 | def delete_sms_params_transformer(_, parameters): 56 | join_params(parameters, ['sch_del']) 57 | 58 | if 'id' in parameters: 59 | parameters['sch_del'] = parameters.pop('id') 60 | 61 | return parameters 62 | 63 | 64 | class Sms(Api): 65 | 66 | path = 'sms.do' 67 | 68 | send = bind_api_endpoint( 69 | method='POST', 70 | path=path, 71 | mapping=(SendResult, SmsSendResult), 72 | accept_parameters=sms_parameters + ['to'], 73 | force_parameters=response_format_param, 74 | exception_class=SendException, 75 | parameters_transformer=parameters_transformer 76 | ) 77 | 78 | send_fast = bind_api_endpoint( 79 | method='POST', 80 | path=path, 81 | mapping=(SendResult, SmsSendResult), 82 | accept_parameters=sms_parameters + ['to'], 83 | force_parameters=fast_force_params, 84 | exception_class=SendException, 85 | parameters_transformer=parameters_transformer 86 | ) 87 | 88 | send_flash = bind_api_endpoint( 89 | method='POST', 90 | path=path, 91 | mapping=(SendResult, SmsSendResult), 92 | accept_parameters=sms_parameters + ['to'], 93 | force_parameters=flash_force_params, 94 | exception_class=SendException, 95 | parameters_transformer=parameters_transformer 96 | ) 97 | 98 | send_to_group = bind_api_endpoint( 99 | method='POST', 100 | path=path, 101 | mapping=(SendResult, SmsSendResult), 102 | accept_parameters=sms_parameters + ['group'], 103 | force_parameters=response_format_param, 104 | exception_class=SendException, 105 | parameters_transformer=parameters_transformer 106 | ) 107 | 108 | remove_scheduled = bind_api_endpoint( 109 | method='POST', 110 | path=path, 111 | mapping=(RemoveMessageResult, ResultCollection), 112 | accept_parameters=['id'], 113 | force_parameters=response_format_param, 114 | exception_class=EndpointException, 115 | parameters_transformer=delete_sms_params_transformer 116 | ) 117 | -------------------------------------------------------------------------------- /smsapi/sms/model.py: -------------------------------------------------------------------------------- 1 | from smsapi.models import ResultCollection 2 | 3 | 4 | class SmsSendResult(ResultCollection): 5 | 6 | def __init__(self, count, results, message=None, length=None, parts=None): 7 | super(SmsSendResult, self).__init__(count, results) 8 | 9 | self.message = message 10 | self.parts = parts 11 | self.length = length 12 | 13 | @classmethod 14 | def parse(cls, json_response, model): 15 | count = json_response.get('count') 16 | message = json_response.get('message') 17 | length = json_response.get('length') 18 | parts = json_response.get('parts') 19 | 20 | sms_list = json_response.get('list', []) 21 | 22 | collection = [] 23 | 24 | for sms in sms_list: 25 | m = model.from_dict(sms) 26 | collection.append(m) 27 | 28 | return cls(count, collection, message=message, length=length, parts=parts) 29 | -------------------------------------------------------------------------------- /smsapi/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime, timedelta, tzinfo 3 | 4 | date_format = "%Y-%m-%d" 5 | 6 | iso8601_datetime_format = '%Y-%m-%dT%H:%M:%S%z' 7 | 8 | 9 | class FixedOffset(tzinfo): 10 | def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"): 11 | super(FixedOffset, self).__init__() 12 | 13 | self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) 14 | self.__name = name 15 | 16 | def utcoffset(self, dt): 17 | return self.__offset 18 | 19 | def tzname(self, dt): 20 | return self.__name 21 | 22 | def dst(self, dt): 23 | return timedelta(0) 24 | 25 | 26 | def convert_to_utf8_str(data): 27 | if isinstance(data, (list, tuple)): 28 | data = [str(d) for d in data] 29 | elif not isinstance(data, str): 30 | data = str(data) 31 | return data 32 | 33 | 34 | def convert_iso8601_str_to_datetime(iso8601_str): 35 | utc_offset = '00:00' 36 | utc_offset_sign = '+' 37 | utc_offset_hours = '0' 38 | utc_offset_minutes = '0' 39 | 40 | date_str, time_str = iso8601_str.split('T') 41 | 42 | time_str_s = re.split(r"([Z+|-])", time_str) 43 | time_str = time_str_s[0] 44 | 45 | if len(time_str_s) == 3 and all(p for p in time_str_s): 46 | utc_offset_sign = time_str_s[1] 47 | utc_offset = time_str_s[2] 48 | 49 | if utc_offset: 50 | utc_offset = utc_offset.replace(':', '') 51 | utc_offset_hours, utc_offset_minutes = utc_offset[:2], utc_offset[2:] 52 | 53 | date = date_str.split('-') 54 | time = time_str.split(':') 55 | 56 | fixed_offset = FixedOffset(int(utc_offset_sign + utc_offset_hours), int(utc_offset_sign + utc_offset_minutes)) 57 | 58 | return datetime(int(date[0]), int(date[1]), int(date[2]), 59 | int(time[0]), int(time[1]), int(time[2]), 60 | tzinfo=fixed_offset) 61 | 62 | 63 | def convert_date_str_to_date(date_str): 64 | return datetime.strptime(date_str, date_format).date() 65 | 66 | 67 | def is_http_success(status_code): 68 | return not int(str(status_code)[:2]) != 20 69 | 70 | 71 | def join_params(parameters, names, sep = ','): 72 | for n in names: 73 | if n in parameters and isinstance(parameters[n], list): 74 | parameters[n] = sep.join(parameters[n]) 75 | 76 | 77 | def update_dict(d, u): 78 | c = d.copy() 79 | c.update(u) 80 | return c 81 | 82 | 83 | def filter_dict(d): 84 | return {k: v for k, v in d.items() if v is not None} 85 | 86 | 87 | def dict_replace(d, k, kv): 88 | if k in d: 89 | del d[k] 90 | d.update(kv) 91 | return d 92 | -------------------------------------------------------------------------------- /smsapi/vms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/smsapi/vms/__init__.py -------------------------------------------------------------------------------- /smsapi/vms/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from smsapi.api import Api 4 | from smsapi.endpoint import bind_api_endpoint 5 | from smsapi.exception import EndpointException, SendException 6 | from smsapi.models import SendResult, ResultCollection, RemoveMessageResult 7 | from smsapi.sms import response_format_param 8 | from smsapi.utils import join_params 9 | 10 | accept_parameters = [ 11 | 'from', 12 | 'tts', 13 | 'file', 14 | 'tts_lector', 15 | 'date', 16 | 'date_validate', 17 | 'try_', 18 | 'interval', 19 | 'skip_gms', 20 | 'idx', 21 | 'check_idx', 22 | 'notify_url', 23 | 'test' 24 | ] 25 | 26 | 27 | def parameters_transformer(api_endpoint, parameters): 28 | join_params(parameters, ['idx'], '|') 29 | 30 | if 'try_' in parameters: 31 | parameters['try'] = parameters.pop('try_') 32 | 33 | if 'file' in parameters and os.path.isfile(parameters.get('file')): 34 | api_endpoint.add_file(open(parameters.pop('file'), 'rb')) 35 | 36 | return parameters 37 | 38 | 39 | def delete_sms_params_transformer(_, parameters): 40 | join_params(parameters, ['sch_del']) 41 | 42 | if 'id' in parameters: 43 | parameters['sch_del'] = parameters.pop('id') 44 | 45 | return parameters 46 | 47 | 48 | class Vms(Api): 49 | 50 | path = 'vms.do' 51 | 52 | send = bind_api_endpoint( 53 | method='POST', 54 | path=path, 55 | mapping=(SendResult, ResultCollection), 56 | accept_parameters=accept_parameters + ['to'], 57 | force_parameters=response_format_param, 58 | exception_class=SendException, 59 | parameters_transformer=parameters_transformer 60 | ) 61 | 62 | send_to_group = bind_api_endpoint( 63 | method='POST', 64 | path=path, 65 | mapping=(SendResult, ResultCollection), 66 | accept_parameters=accept_parameters + ['group'], 67 | force_parameters=response_format_param, 68 | exception_class=SendException, 69 | parameters_transformer=parameters_transformer 70 | ) 71 | 72 | remove_scheduled = bind_api_endpoint( 73 | method='POST', 74 | path=path, 75 | mapping=(RemoveMessageResult, ResultCollection), 76 | accept_parameters=['id'], 77 | force_parameters=response_format_param, 78 | exception_class=EndpointException, 79 | parameters_transformer=delete_sms_params_transformer 80 | ) 81 | 82 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | import unittest 6 | import pkgutil 7 | import requests 8 | 9 | from smsapi.api import Api 10 | from smsapi.client import SmsApiPlClient 11 | from smsapi.models import ResultCollection 12 | from smsapi.sms import response_format_param 13 | from smsapi.sms.model import SmsSendResult 14 | 15 | from tests.unit.doubles import ApiSpy, request_fake 16 | from tests.unit.fixtures import create_send_result 17 | 18 | requests.request = request_fake 19 | 20 | 21 | class SmsApiTestCase(unittest.TestCase): 22 | 23 | def setUp(self): 24 | self.request_fake = request_fake 25 | 26 | self.client = SmsApiPlClient(access_token='some-access-token') 27 | 28 | spy_endpoints(self.client) 29 | 30 | def load_fixture(self, dir, fixture): 31 | with open(os.path.abspath(os.path.dirname(__file__)) + '/%s/fixtures/%s.json' % (dir, fixture)) as f: 32 | data = f .read() 33 | 34 | return json.loads(data) 35 | 36 | def assertParamsForwardedToRequestEquals(self, params, *args): 37 | for d in args: 38 | params.update(d or {}) 39 | 40 | params.update(response_format_param) 41 | 42 | self.assertEqual(params, self.request_fake.payload) 43 | 44 | def assertRequestPayloadContains(self, key, value): 45 | self.assertIn(key, self.request_fake.payload.keys()) 46 | self.assertIn(value, self.request_fake.payload.values()) 47 | 48 | def assertSendResultForNumberEquals(self, number, result, result_class=ResultCollection): 49 | numbers = number if isinstance(number, list) else [number] 50 | 51 | expected_result = result_class(len(numbers), [create_send_result(n) for n in numbers]) 52 | 53 | self.assertEqual(expected_result, result) 54 | 55 | def assertSmsSendResultForNumberEquals(self, number, result): 56 | self.assertSendResultForNumberEquals(number, result, result_class=SmsSendResult) 57 | 58 | def assertRequestMethodIsPost(self): 59 | self.assertEqual("POST", self.request_fake.http_method) 60 | 61 | def assertRequestMethodIsPut(self): 62 | self.assertEqual("PUT", self.request_fake.http_method) 63 | 64 | 65 | def spy_endpoints(client): 66 | 67 | for attr in client.__dict__: 68 | if isinstance(client.__dict__[attr], Api): 69 | client.__dict__[attr] = ApiSpy(client.__dict__[attr]) 70 | 71 | 72 | def import_from_string(name): 73 | 74 | if '.' in name: 75 | module, pkg = name.rsplit(".", 1) 76 | else: 77 | return __import__(name) 78 | 79 | return getattr(__import__(module, None, None, [pkg]), pkg) 80 | 81 | 82 | def app_test_suites(module_name): 83 | 84 | module = import_from_string(module_name) 85 | 86 | path = getattr(module, '__path__', None) 87 | 88 | if not path: 89 | raise ValueError('%s is not a package' % module) 90 | 91 | basename = module.__name__ + '.' 92 | 93 | for importer, module_name, is_pkg in pkgutil.iter_modules(path): 94 | module_name = basename + module_name 95 | 96 | if is_pkg: 97 | for suite in app_test_suites(module_name): 98 | yield suite 99 | 100 | module = import_from_string(module_name) 101 | 102 | if hasattr(module, 'suite'): 103 | yield module.suite() 104 | 105 | 106 | def suite(): 107 | suite = unittest.TestSuite() 108 | for _suite in app_test_suites(__name__): 109 | suite.addTest(_suite) 110 | return suite 111 | 112 | 113 | def run_tests(): 114 | try: 115 | unittest.TextTestRunner(verbosity=2).run(suite()) 116 | except Exception as e: 117 | print('Error: %s' % e) 118 | 119 | 120 | if __name__ == '__main__': 121 | run_tests() -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/account/__init__.py -------------------------------------------------------------------------------- /tests/unit/account/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from smsapi.account.models import AccountBalanceResult, UserResult 4 | from smsapi.models import ResultCollection 5 | 6 | from tests import SmsApiTestCase 7 | from tests.unit.doubles import api_response_fixture 8 | 9 | 10 | class AccountApiTest(SmsApiTestCase): 11 | 12 | @api_response_fixture('balance', 'account') 13 | def test_account_balance(self): 14 | result = self.client.account.balance() 15 | 16 | expected_result = AccountBalanceResult( 17 | points=100.00, 18 | pro_count=606, 19 | eco_count=1428, 20 | mms_count=333, 21 | vms_gsm_count=476, 22 | vms_land_count=714 23 | ) 24 | 25 | self.assertParamsForwardedToRequestEquals({'credits': 1, 'details': 1}) 26 | self.assertEqual(expected_result, result) 27 | 28 | @api_response_fixture('user', 'account') 29 | def test_create_user(self): 30 | name, password, api_password = 'some', 'some-password', 'some-api-password' 31 | 32 | result = self.client.account.create_user(name=name, password=password, api_password=api_password) 33 | 34 | expected_result = create_user_result() 35 | 36 | self.assertRequestMethodIsPost() 37 | self.assertEqual(expected_result, result) 38 | self.assertRequestPayloadContains('add_user', name) 39 | self.assertRequestPayloadContains('pass', password) 40 | self.assertRequestPayloadContains('pass_api', api_password) 41 | 42 | @api_response_fixture('user', 'account') 43 | def test_update_user(self): 44 | name, password = 'some', 'some-password' 45 | 46 | result = self.client.account.update_user(name=name, password=password) 47 | 48 | expected_result = create_user_result() 49 | 50 | self.assertRequestMethodIsPut() 51 | self.assertEqual(expected_result, result) 52 | self.assertRequestPayloadContains('set_user', name) 53 | self.assertRequestPayloadContains('pass', password) 54 | 55 | @api_response_fixture('user', 'account') 56 | def test_get_user(self): 57 | name = 'some' 58 | 59 | result = self.client.account.user(name=name) 60 | 61 | expected_result = create_user_result() 62 | 63 | self.assertEqual(expected_result, result) 64 | self.assertParamsForwardedToRequestEquals({'get_user': name}) 65 | 66 | @api_response_fixture('users_list', 'account') 67 | def test_list_users(self): 68 | result = self.client.account.list_users() 69 | 70 | expected_result = ResultCollection(2, [ 71 | create_user_result(name='some'), 72 | create_user_result(name='any'), 73 | ]) 74 | 75 | self.assertEqual(expected_result, result) 76 | self.assertParamsForwardedToRequestEquals({'list': 1}) 77 | 78 | 79 | def create_user_result(name='some'): 80 | return UserResult(username=name, limit=0, month_limit=0, 81 | senders=0, phonebook=0, active=False, info="some") 82 | 83 | 84 | def suite(): 85 | suite = unittest.TestSuite() 86 | suite.addTest(unittest.makeSuite(AccountApiTest)) 87 | return suite 88 | -------------------------------------------------------------------------------- /tests/unit/account/fixtures/balance.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "points": 100.00, 5 | "proCount": 606, 6 | "ecoCount": 1428, 7 | "mmsCount": 333, 8 | "vmsGsmCount": 476, 9 | "vmsLandCount": 714 10 | } 11 | } -------------------------------------------------------------------------------- /tests/unit/account/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "username": "some", 5 | "limit": 0, 6 | "month_limit": 0, 7 | "senders": 0, 8 | "phonebook": 0, 9 | "active": false, 10 | "info": "some" 11 | } 12 | } -------------------------------------------------------------------------------- /tests/unit/account/fixtures/users_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": [ 4 | { 5 | "username": "some", 6 | "limit": 0, 7 | "month_limit": 0, 8 | "senders": 0, 9 | "phonebook": 0, 10 | "active": false, 11 | "info": "some" 12 | }, 13 | { 14 | "username": "any", 15 | "limit": 0, 16 | "month_limit": 0, 17 | "senders": 0, 18 | "phonebook": 0, 19 | "active": false, 20 | "info": "some" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /tests/unit/blacklist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/blacklist/__init__.py -------------------------------------------------------------------------------- /tests/unit/blacklist/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from smsapi.blacklist.models import BlacklistPhoneNumber 4 | from smsapi.models import ModelCollection 5 | from tests import SmsApiTestCase 6 | from tests.unit.doubles import api_response_fixture 7 | 8 | 9 | class BlacklistApiTest(SmsApiTestCase): 10 | 11 | @api_response_fixture("phonenumber", "blacklist") 12 | def test_add_phone_number_to_blacklist(self): 13 | phone_number = "654543432" 14 | expire_at = "2060-01-01T20:00:00Z" 15 | 16 | result = self.client.blacklist.add_phone_number(phone_number=phone_number, expire_at=expire_at) 17 | 18 | expected_result = BlacklistPhoneNumber( 19 | id="1", 20 | phone_number="654543432", 21 | expire_at=expire_at, 22 | created_at="2017-07-21T17:32:28Z" 23 | ) 24 | 25 | self.assertEqual('POST', self.request_fake.http_method) 26 | self.assertEqual({"phone_number": phone_number, "expire_at": expire_at}, self.request_fake.data) 27 | self.assertTrue(self.request_fake.url.endswith('blacklist/phone_numbers')) 28 | self.assertEqual(expected_result, result) 29 | 30 | def test_delete_phone_number_from_blacklist(self): 31 | phone_number_blacklist_id = "1" 32 | 33 | self.client.blacklist.delete_phone_number(id=phone_number_blacklist_id) 34 | 35 | self.assertEqual("DELETE", self.request_fake.http_method) 36 | self.assertTrue(self.request_fake.url.endswith("blacklist/phone_numbers/1")) 37 | 38 | def test_delete_all_phone_numbers_from_blacklist(self): 39 | self.client.blacklist.delete_all_phone_numbers() 40 | 41 | self.assertEqual("DELETE", self.request_fake.http_method) 42 | self.assertTrue(self.request_fake.url.endswith("blacklist/phone_numbers")) 43 | 44 | @api_response_fixture("collection", "blacklist") 45 | def test_get_all_phone_numbers_from_blacklist(self): 46 | result = self.client.blacklist.list_phone_numbers() 47 | 48 | expected_result = ModelCollection(1, [ 49 | BlacklistPhoneNumber( 50 | id="1", 51 | phone_number="654543432", 52 | expire_at="2060-01-01T20:00:00Z", 53 | created_at="2017-07-21T17:32:28Z" 54 | ) 55 | ]) 56 | 57 | self.assertEqual(expected_result, result) 58 | self.assertEqual("GET", self.request_fake.http_method) 59 | self.assertTrue(self.request_fake.url.endswith("blacklist/phone_numbers")) 60 | 61 | 62 | def suite(): 63 | s = unittest.TestSuite() 64 | s.addTest(unittest.makeSuite(BlacklistApiTest)) 65 | return s 66 | -------------------------------------------------------------------------------- /tests/unit/blacklist/fixtures/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "id": "1", 8 | "phone_number": "654543432", 9 | "expire_at": "2060-01-01T20:00:00Z", 10 | "created_at": "2017-07-21T17:32:28Z" 11 | } 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /tests/unit/blacklist/fixtures/delete_all_phone_numbers.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/blacklist/fixtures/delete_phone_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/blacklist/fixtures/phonenumber.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "1", 5 | "phone_number": "654543432", 6 | "expire_at": "2060-01-01T20:00:00Z", 7 | "created_at": "2017-07-21T17:32:28Z" 8 | } 9 | } -------------------------------------------------------------------------------- /tests/unit/client_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from requests.auth import HTTPBasicAuth 5 | 6 | from smsapi import lib_info 7 | from smsapi.auth import BearerAuth 8 | from smsapi.client import Client 9 | from smsapi.compat import py_version 10 | from smsapi.exception import ClientException 11 | from tests import SmsApiTestCase 12 | 13 | 14 | class ClientTest(SmsApiTestCase): 15 | 16 | def test_use_bearer_auth_when_access_token_provided(self): 17 | token = 'some-token' 18 | 19 | client = Client('some-domain', access_token=token) 20 | 21 | self.assertEqual(BearerAuth(access_token=token), client.auth) 22 | 23 | def test_use_basic_auth_when_credentials_provided(self): 24 | username = 'some-username' 25 | password = 'some-password' 26 | 27 | client = Client('some-domain', None, username=username, password=password) 28 | 29 | self.assertIsInstance(client.auth, HTTPBasicAuth) 30 | self.assertEqual(username, client.auth.username) 31 | self.assertEqual(password, client.auth.password) 32 | 33 | 34 | def test_error_when_create_client_without_any_credentials(self): 35 | self.assertRaises(ClientException, Client, 'some-domain') 36 | 37 | def test_add_request_id_for_http_requests(self): 38 | self.make_any_client_call() 39 | 40 | self.assertTrue('X-Request-Id' in self.request_fake.headers) 41 | 42 | def test_add_request_header_with_library_and_python_version(self): 43 | self.make_any_client_call() 44 | 45 | expected_user_agent_header = {'User-Agent': '%s (Python%s)' % (lib_info, py_version)} 46 | 47 | self.assertDictContainsSubset(expected_user_agent_header, self.request_fake.headers) 48 | 49 | def make_any_client_call(self): 50 | self.client.sms.send() 51 | 52 | 53 | def suite(): 54 | suite = unittest.TestSuite() 55 | suite.addTest(unittest.makeSuite(ClientTest)) 56 | return suite -------------------------------------------------------------------------------- /tests/unit/contacts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/contacts/__init__.py -------------------------------------------------------------------------------- /tests/unit/contacts/api_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from datetime import datetime, date 5 | from smsapi.contacts.models import ContactModel, CustomFieldModel 6 | from tests import SmsApiTestCase 7 | from tests.unit.contacts.fixtures import create_collection_from_fixture 8 | 9 | 10 | class ContactsApiTest(SmsApiTestCase): 11 | 12 | def test_create_contact(self): 13 | contact = self.client.contacts.create_contact( 14 | first_name='Jon', 15 | phone_number=987654321, 16 | email='jondoe@somedomain.com') 17 | 18 | self.assertIsInstance(contact, ContactModel) 19 | self.assertIsInstance(contact.date_created, datetime) 20 | self.assertIsInstance(contact.birthday_date, date) 21 | 22 | def test_update_contact(self): 23 | contact = self.client.contacts.update_contact(contact_id=1, last_name='Doe') 24 | 25 | self.assertEqual(contact.last_name, 'Doe') 26 | 27 | def test_list_contacts(self): 28 | contacts = self.client.contacts.list_contacts() 29 | 30 | expected_contacts = create_collection_from_fixture('list_contacts', ContactModel) 31 | 32 | self.assertEqual(contacts, expected_contacts) 33 | 34 | def test_get_contact(self): 35 | self.client.contacts.get_contact(contact_id=1) 36 | 37 | def test_delete_contact(self): 38 | self.client.contacts.delete_contact(contact_id=1) 39 | 40 | def test_list_contacts_groups(self): 41 | self.client.contacts.list_contact_groups(contact_id=1) 42 | 43 | def test_list_groups(self): 44 | self.client.contacts.list_groups() 45 | 46 | self.assertEqual(self.client.domain + 'contacts/groups', self.request_fake.url) 47 | self.assertEqual({'with': 'contacts_count'}, self.request_fake.params) 48 | 49 | def test_create_group(self): 50 | self.client.contacts.create_group() 51 | 52 | def test_delete_group(self): 53 | self.client.contacts.delete_group(group_id=1) 54 | 55 | def test_get_group(self): 56 | self.client.contacts.get_group(group_id=1) 57 | 58 | self.assertEqual(self.client.contacts.client.domain + 'contacts/groups/1', self.request_fake.url) 59 | self.assertEqual({'with': 'contacts_count'}, self.request_fake.params) 60 | 61 | def test_update_group(self): 62 | self.client.contacts.update_group(group_id=1) 63 | 64 | def test_list_group_permission(self): 65 | self.client.contacts.list_group_permissions(group_id=1) 66 | 67 | def test_create_group_permission(self): 68 | self.client.contacts.create_group_permission(group_id=1) 69 | 70 | def test_list_user_group_permission(self): 71 | self.client.contacts.list_user_group_permissions(group_id=1, username='test') 72 | 73 | def test_delete_user_group_permission(self): 74 | self.client.contacts.delete_user_group_permission(group_id=1, username='test') 75 | 76 | def test_update_user_group_permission(self): 77 | self.client.contacts.update_user_group_permission(group_id=1, username='test') 78 | 79 | def test_unpin_contact_from_group(self): 80 | self.client.contacts.unpin_contact_from_group(group_id=1, contact_id=1) 81 | 82 | def test_contact_is_in_group(self): 83 | self.client.contacts.contact_is_in_group(group_id=1, contact_id=1) 84 | 85 | def test_pin_contact_to_group(self): 86 | self.client.contacts.pin_contact_to_group(group_id=1, contact_id=1) 87 | 88 | def test_list_custom_fields(self): 89 | r = self.client.contacts.list_custom_fields() 90 | 91 | expected_collection = create_collection_from_fixture('list_custom_fields', CustomFieldModel) 92 | 93 | self.assertEqual(expected_collection, r) 94 | 95 | def test_create_custom_field(self): 96 | self.client.contacts.create_custom_field() 97 | 98 | def test_delete_custom_field(self): 99 | self.client.contacts.delete_custom_field(field_id=1) 100 | 101 | self.assertEqual('DELETE', self.request_fake.http_method) 102 | self.assertTrue(self.request_fake.url.endswith('contacts/fields/1')) 103 | 104 | def test_update_custom_field(self): 105 | self.client.contacts.update_custom_field(field_id=1, name='test_f') 106 | 107 | self.assertEqual('PUT', self.request_fake.http_method) 108 | self.assertTrue(self.request_fake.url.endswith('contacts/fields/1')) 109 | self.assertRequestPayloadContains("name", "test_f") 110 | 111 | def test_unpin_contact_from_group_by_query(self): 112 | number, email = '100200300', 'some@email.com' 113 | 114 | args = {'phone_number': number, 'email': email, 'q': 'any text'} 115 | 116 | self.client.contacts.unpin_contact_from_group_by_query(group_id=1, **args) 117 | 118 | self.assertEqual(args, self.request_fake.data) 119 | 120 | def test_count_contacts_in_trash(self): 121 | r = self.client.contacts.count_contacts_in_trash() 122 | 123 | self.assertEqual(2, r) 124 | 125 | def test_restore_contacts_in_trash(self): 126 | self.client.contacts.restore_contacts_in_trash() 127 | 128 | self.assertEqual('PUT', self.request_fake.http_method) 129 | self.assertTrue(self.request_fake.url.endswith('contacts/trash/restore')) 130 | 131 | def test_clean_trash(self): 132 | self.client.contacts.clean_trash() 133 | 134 | 135 | def suite(): 136 | suite = unittest.TestSuite() 137 | suite.addTest(unittest.makeSuite(ContactsApiTest)) 138 | return suite 139 | -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | 6 | from smsapi.models import ModelCollection 7 | 8 | 9 | def create_collection_from_fixture(fixture, model): 10 | list_contacts = open(os.path.join(os.path.dirname(__file__), 'fixtures/%s.json' % fixture)) 11 | 12 | r = json.load(list_contacts).get('response') 13 | 14 | c = ModelCollection(r['size'], list(map(lambda x: model(**x), r['collection']))) 15 | 16 | return c 17 | -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/clean_trash.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/contact_is_in_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576caf00cf2f41201a193ac", 5 | "idx": null, 6 | "first_name": "Jon", 7 | "last_name": "", 8 | "birthday_date": "1986-10-04", 9 | "phone_number": "48987654321", 10 | "gender": "male", 11 | "city": null, 12 | "email": "jondoe@somedomain.com", 13 | "source": null, 14 | "date_created": "2015-06-09T14:08:13+02:00", 15 | "date_updated": "2015-06-09T13:18:02+02:00", 16 | "description": "Jon Doe" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/count_contacts_in_trash.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204, 3 | "headers": { 4 | "X-Result-Count": 2 5 | } 6 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/create_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "5576caf00cf2f41201a193ac", 5 | "idx": null, 6 | "first_name": "Jon", 7 | "last_name": "Doe", 8 | "birthday_date": "1986-10-04", 9 | "phone_number": "48987654321", 10 | "gender": "male", 11 | "city": null, 12 | "email": "jondoe@somedomain.com", 13 | "source": null, 14 | "date_created": "2015-06-09T13:16:00+02:00", 15 | "date_updated": "2015-06-09T13:16:00+02:00", 16 | "description": "Jon Doe" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/create_custom_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "5576d8e00cf2f41201a193b0", 5 | "name": "test", 6 | "type": "TEXT" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/create_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "5576cbf90cf2f41201a193ad", 5 | "name": "group1", 6 | "contacts_count": 0, 7 | "date_created": "2015-06-09T13:20:25+02:00", 8 | "date_updated": "2015-06-09T13:20:25+02:00", 9 | "description": "group1", 10 | "created_by": "j.doe", 11 | "idx": null, 12 | "permissions": [ 13 | { 14 | "username": "j.doe", 15 | "group_id": "5576cbf90cf2f41201a193ad", 16 | "write": true, 17 | "read": true, 18 | "send": true 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/create_group_permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "username": "j.doe1", 5 | "group_id": "5576cbf90cf2f41201a193ad", 6 | "write": false, 7 | "read": true, 8 | "send": false 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/delete_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/delete_custom_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/delete_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/delete_user_group_permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/get_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576caf00cf2f41201a193ac", 5 | "idx": null, 6 | "first_name": "Jon", 7 | "last_name": "", 8 | "birthday_date": "1986-10-04", 9 | "phone_number": "48987654321", 10 | "gender": "male", 11 | "city": null, 12 | "email": "jondoe@somedomain.com", 13 | "source": null, 14 | "date_created": "2015-06-09T13:16:00+02:00", 15 | "date_updated": "2015-06-09T13:16:00+02:00", 16 | "description": "Jon Doe" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/get_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576cbf90cf2f41201a193ad", 5 | "name": "group1", 6 | "contacts_count": 0, 7 | "date_created": "2015-06-09T13:20:25+02:00", 8 | "date_updated": "2015-06-09T13:20:25+02:00", 9 | "description": "group1", 10 | "created_by": "j.doe", 11 | "idx": null, 12 | "permissions": [ 13 | { 14 | "username": "j.doe", 15 | "group_id": "5576cbf90cf2f41201a193ad", 16 | "write": true, 17 | "read": true, 18 | "send": true 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_contact_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "id": "5576cbf90cf2f41201a193ad", 8 | "name": "group1", 9 | "contacts_count": 1, 10 | "date_created": "2016-11-26T16:25:00+01:00", 11 | "date_updated": "2016-11-26T16:25:00+01:00", 12 | "description": "group1", 13 | "created_by": "j.doe", 14 | "idx": null, 15 | "permissions": [ 16 | { 17 | "username": "j.doe", 18 | "group_id": "5576cbf90cf2f41201a193ad", 19 | "write": true, 20 | "read": true, 21 | "send": true 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_contacts.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response":{ 4 | "size": 2, 5 | "collection": [ 6 | { 7 | "id": "5576caf00cf2f41201a193ac", 8 | "idx": null, 9 | "first_name": "Jon", 10 | "last_name": "", 11 | "birthday_date": "1986-10-04", 12 | "phone_number": "48987654321", 13 | "gender": "male", 14 | "city": null, 15 | "email": "jondoe@somedomain.com", 16 | "source": null, 17 | "date_created": "2015-06-09T13:18:02+02:00", 18 | "date_updated": "2015-06-09T13:18:02+02:00", 19 | "description": "Jon Doe" 20 | }, 21 | { 22 | "id": "5576ce8e0cf2f41201a193af", 23 | "idx": null, 24 | "first_name": "Joan", 25 | "last_name": "Doe", 26 | "birthday_date": "1985-06-26", 27 | "phone_number": "48987654321", 28 | "gender": "female", 29 | "city": null, 30 | "email": "joandoe@somedomain.com", 31 | "source": null, 32 | "date_created": "2015-06-09T13:31:26+02:00", 33 | "date_updated": "2015-06-09T13:31:26+02:00", 34 | "description": "Joan Doe" 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_custom_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "id": "5576d8e00cf2f41201a193b0", 8 | "name": "test", 9 | "type": "TEXT" 10 | } 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_group_permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 2, 5 | "collection": [ 6 | { 7 | "username": "j.doe", 8 | "group_id": "5576cbf90cf2f41201a193ad", 9 | "write": true, 10 | "read": true, 11 | "send": true 12 | }, 13 | { 14 | "username": "j.doe1", 15 | "group_id": "5576cbf90cf2f41201a193ad", 16 | "write": false, 17 | "read": true, 18 | "send": false 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 2, 5 | "collection": [ 6 | { 7 | "id": "5576cbf90cf2f41201a193ad", 8 | "name": "group1", 9 | "contacts_count": 0, 10 | "date_created": "2016-11-26T16:25:00+01:00", 11 | "date_updated": "2016-11-26T16:25:00+01:00", 12 | "description": "group1", 13 | "created_by": "j.doe", 14 | "idx": null, 15 | "permissions": [ 16 | { 17 | "username": "j.doe", 18 | "group_id": "5576cbf90cf2f41201a193ad", 19 | "write": true, 20 | "read": true, 21 | "send": true 22 | } 23 | ] 24 | }, 25 | { 26 | "id": "5576cc620cf2f41201a193ae", 27 | "name": "group2", 28 | "contacts_count": 0, 29 | "date_created": "2016-11-26T17:30:00+01:00", 30 | "date_updated": "2016-11-26T17:30:00+01:00", 31 | "description": "group2", 32 | "created_by": "j.doe", 33 | "idx": null, 34 | "permissions": [ 35 | { 36 | "username": "j.doe", 37 | "group_id": "5576cc620cf2f41201a193ae", 38 | "write": true, 39 | "read": true, 40 | "send": true 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/list_user_group_permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | 5 | } 6 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/pin_contact_to_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "5576caf00cf2f41201a193ac", 5 | "idx": null, 6 | "first_name": "Jon", 7 | "last_name": "", 8 | "birthday_date": "1986-10-04", 9 | "phone_number": "48987654321", 10 | "gender": "male", 11 | "city": null, 12 | "email": "jondoe@somedomain.com", 13 | "source": null, 14 | "date_created": "2015-06-09T13:18:02+02:00", 15 | "date_updated": "2015-06-09T13:18:02+02:00", 16 | "description": "Ja udpated" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/restore_contacts_in_trash.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/unpin_contact_from_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/unpin_contact_from_group_by_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/update_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576caf00cf2f41201a193ac", 5 | "idx": null, 6 | "first_name": "Jon", 7 | "last_name": "Doe", 8 | "birthday_date": "1986-10-04", 9 | "phone_number": "48987654321", 10 | "gender": "male", 11 | "city": null, 12 | "email": "jondoe@somedomain.com", 13 | "source": null, 14 | "date_created": "2015-06-09T13:16:00+02:00", 15 | "date_updated": "2015-06-09T13:16:00+02:00", 16 | "description": "Jon Doe" 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/update_custom_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576d8e00cf2f41201a193b0", 5 | "name": "test_f", 6 | "type": "TEXT" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/update_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "id": "5576cbf90cf2f41201a193ad", 5 | "name": "group1", 6 | "contacts_count": 0, 7 | "date_created": "2015-06-09T13:20:25+02:00", 8 | "date_updated": "2015-06-09T13:20:25+02:00", 9 | "description": "group1", 10 | "created_by": "j.doe", 11 | "idx": null, 12 | "permissions": [ 13 | { 14 | "username": "j.doe", 15 | "group_id": "5576cbf90cf2f41201a193ad", 16 | "write": true, 17 | "read": true, 18 | "send": true 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/contacts/fixtures/update_user_group_permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "username": "j.doe1", 5 | "group_id": "5576cbf90cf2f41201a193ad", 6 | "write": true, 7 | "read": true, 8 | "send": false 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/doubles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | import types 6 | 7 | from smsapi.compat import urlparse 8 | 9 | 10 | class RequestFake(object): 11 | 12 | last_called_api_method = None 13 | 14 | fixtures_base_dir = None 15 | 16 | def __call__(self, *args, **kwargs): 17 | self.http_method, self.url = args 18 | self.headers = kwargs.get('headers') 19 | self.data = kwargs.get('data') 20 | self.files = kwargs.get('files') 21 | self.params = kwargs.get('params') 22 | 23 | dir = os.path.abspath(os.path.dirname(__file__)) 24 | 25 | parsed_url = urlparse(self.url) 26 | 27 | if self.fixtures_base_dir: 28 | api = self.fixtures_base_dir 29 | else: 30 | api = parsed_url.path.split("/")[1].split('.')[0] 31 | 32 | with open('%s/%s/fixtures/%s.json' % (dir, api, self.last_called_api_method)) as f: 33 | response = json.loads(f.read()) 34 | 35 | status_code = response.get('status_code') 36 | content = response.get('response') 37 | headers = response.get('headers') 38 | 39 | self.last_called_api_method = None 40 | self.fixtures_base_dir = None 41 | 42 | return ResponseMock(status_code, content, headers) 43 | 44 | @property 45 | def payload(self): 46 | return self.params if self.http_method == 'GET' else self.data 47 | 48 | 49 | class ResponseMock(object): 50 | 51 | force_status_code = None 52 | 53 | def __init__(self, status_code, content, headers): 54 | super(ResponseMock, self).__init__() 55 | 56 | self.code = status_code 57 | self.content = content 58 | self.headers = headers or {} 59 | 60 | @property 61 | def status_code(self): 62 | if self.force_status_code: 63 | return self.force_status_code 64 | return self.code 65 | 66 | def json(self): 67 | return self.content 68 | 69 | 70 | class ApiSpy(object): 71 | 72 | def __init__(self, api): 73 | super(ApiSpy, self).__init__() 74 | 75 | self.api = api 76 | 77 | def __getattr__(self, item): 78 | 79 | attr = getattr(self.api, item) 80 | 81 | if isinstance(attr, types.MethodType) and request_fake.last_called_api_method is None: 82 | request_fake.last_called_api_method = item 83 | 84 | return attr 85 | 86 | 87 | def api_response_fixture(fixture, dir = None): 88 | def decorator(func): 89 | def func_wrapper(name): 90 | request_fake.fixtures_base_dir = dir 91 | request_fake.last_called_api_method = fixture 92 | return func(name) 93 | return func_wrapper 94 | return decorator 95 | 96 | 97 | request_fake = RequestFake() -------------------------------------------------------------------------------- /tests/unit/exceptions_test.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import unittest 3 | 4 | from smsapi.exception import ( 5 | ClientException, 6 | EndpointException, 7 | SendException, 8 | SmsApiException, 9 | ) 10 | 11 | 12 | class ExceptionPicklingTest(unittest.TestCase): 13 | """Ensure SMS API custom exceptions are pickleable.""" 14 | 15 | message = 'test exception pickling' 16 | 17 | def test_client_exception_pickling(self): 18 | """Ensure ``ClientException`` is pickleable.""" 19 | self.assert_is_pickleable(ClientException(self.message)) 20 | 21 | def test_endpoint_exception_pickling(self): 22 | """Ensure ``EndpointException`` is pickleable.""" 23 | self.assert_is_pickleable(EndpointException(self.message)) 24 | 25 | def test_send_exception_pickling(self): 26 | """Ensure ``SendException`` is pickleable.""" 27 | self.assert_is_pickleable( 28 | SendException(self.message, code='pickling_error'), 29 | ) 30 | 31 | def test_smsapi_exception_pickling(self): 32 | """Ensure ``SmsApiException`` is pickleable.""" 33 | self.assert_is_pickleable(SmsApiException(self.message)) 34 | 35 | def assert_is_pickleable(self, exception): 36 | """Assert exception is pickleable.""" 37 | pickled_exception = pickle.dumps(exception) 38 | self.assertEqual(pickle.loads(pickled_exception), exception) 39 | 40 | 41 | def suite(): 42 | suite = unittest.TestSuite() 43 | suite.addTest(unittest.makeSuite(ExceptionPicklingTest)) 44 | return suite 45 | -------------------------------------------------------------------------------- /tests/unit/fixtures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from smsapi.models import SendResult 4 | 5 | 6 | def create_send_result(number): 7 | return SendResult( 8 | id="1", 9 | points=0.1, 10 | number=number, 11 | date_sent=1460969712, 12 | submitted_number=number, 13 | status='QUEUE') -------------------------------------------------------------------------------- /tests/unit/hlr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/hlr/__init__.py -------------------------------------------------------------------------------- /tests/unit/hlr/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from smsapi.hlr.api import HlrResult 4 | from tests import SmsApiTestCase 5 | 6 | 7 | class HrlApiTest(SmsApiTestCase): 8 | 9 | def test_check_number(self): 10 | number = '48100200300' 11 | 12 | result = self.client.hlr.check_number(number=number, idx=['id1', 'id2']) 13 | 14 | expected_result = HlrResult(status='OK', number=number, id='1a2', price=0.1) 15 | 16 | self.assertRequestMethodIsPost() 17 | self.assertEqual(expected_result, result) 18 | self.assertRequestPayloadContains('number', number) 19 | self.assertRequestPayloadContains('idx', 'id1|id2') 20 | 21 | 22 | def suite(): 23 | s = unittest.TestSuite() 24 | s.addTest(unittest.makeSuite(HrlApiTest)) 25 | return s 26 | -------------------------------------------------------------------------------- /tests/unit/hlr/fixtures/check_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "status": "OK", 5 | "number": "48100200300", 6 | "id": "1a2", 7 | "price": 0.1 8 | } 9 | } -------------------------------------------------------------------------------- /tests/unit/mfa/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/mfa/__init__.py -------------------------------------------------------------------------------- /tests/unit/mfa/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from tests import SmsApiTestCase 4 | 5 | 6 | class MfaApiTest(SmsApiTestCase): 7 | 8 | def test_send_mfa(self): 9 | number = '48100200300' 10 | 11 | self.client.mfa.send_mfa(phone_number=number) 12 | 13 | self.assertEqual('POST', self.request_fake.http_method) 14 | self.assertParamsForwardedToRequestEquals({ 15 | "phone_number": number 16 | }) 17 | 18 | def test_verify_mfa(self): 19 | code = 123 20 | number = '48100200300' 21 | 22 | self.client.mfa.verify_mfa(phone_number=number, code=code) 23 | 24 | self.assertEqual('POST', self.request_fake.http_method) 25 | self.assertParamsForwardedToRequestEquals({ 26 | "phone_number": number, 27 | "code": "123" 28 | }) 29 | 30 | 31 | def suite(): 32 | suite = unittest.TestSuite() 33 | suite.addTest(unittest.makeSuite(MfaApiTest)) 34 | return suite 35 | -------------------------------------------------------------------------------- /tests/unit/mfa/fixtures/send_mfa.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "string", 5 | "code": "string", 6 | "phone_number": "085590638", 7 | "from": "test" 8 | } 9 | } -------------------------------------------------------------------------------- /tests/unit/mfa/fixtures/verify_mfa.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201 3 | } -------------------------------------------------------------------------------- /tests/unit/mms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/mms/__init__.py -------------------------------------------------------------------------------- /tests/unit/mms/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from smsapi.exception import EndpointException 4 | from smsapi.models import ResultCollection, RemoveMessageResult 5 | from tests import SmsApiTestCase 6 | from tests.unit.doubles import api_response_fixture 7 | 8 | 9 | class MmsApiTest(SmsApiTestCase): 10 | 11 | def test_send_mms(self): 12 | number = '48100200300' 13 | 14 | result = self.client.mms.send(to=number, smil='any', subject='any') 15 | 16 | self.assertRequestMethodIsPost() 17 | self.assertSendResultForNumberEquals(number, result) 18 | self.assertRequestPayloadContains("to", number) 19 | self.assertRequestPayloadContains("smil", 'any') 20 | self.assertRequestPayloadContains("subject", 'any') 21 | 22 | 23 | def test_send_personalized_mms(self): 24 | number = '48100200300' 25 | 26 | self.client.mms.send(to=number, idx= ['id1', 'id2']) 27 | 28 | self.assertRequestMethodIsPost() 29 | self.assertRequestPayloadContains("idx", "id1|id2") 30 | 31 | @api_response_fixture('send') 32 | def test_send_mms_to_group(self): 33 | self.client.mms.send_to_group(group="any") 34 | 35 | self.assertRequestMethodIsPost() 36 | self.assertRequestPayloadContains("group", "any") 37 | 38 | def test_remove_scheduled_mms(self): 39 | result = self.client.mms.remove_scheduled(id='1') 40 | 41 | expected_result = ResultCollection(1, [RemoveMessageResult(id='1')]) 42 | 43 | self.assertRequestMethodIsPost() 44 | self.assertEqual(expected_result, result) 45 | self.assertRequestPayloadContains("sch_del", "1") 46 | 47 | @api_response_fixture('remove_not_exists_mms') 48 | def test_remove_not_exists_mms(self): 49 | exception = None 50 | 51 | try: 52 | self.client.mms.remove_scheduled(id='1') 53 | except EndpointException as e: 54 | exception = e 55 | 56 | expected_exception = EndpointException(u'Not exists ID message', 301) 57 | 58 | self.assertEqual(expected_exception, exception) 59 | 60 | def test_send_test_mms(self): 61 | number = '48100200300' 62 | args = {'to': number, 'test': '1'} 63 | 64 | self.client.mms.send(**args) 65 | 66 | self.assertRequestMethodIsPost() 67 | self.assertRequestPayloadContains("test", "1") 68 | 69 | def suite(): 70 | s = unittest.TestSuite() 71 | s.addTest(unittest.makeSuite(MmsApiTest)) 72 | return s 73 | -------------------------------------------------------------------------------- /tests/unit/mms/fixtures/remove_not_exists_mms.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "error": 301, 5 | "message": "Not exists ID message" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/unit/mms/fixtures/remove_scheduled.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1" 8 | } 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /tests/unit/mms/fixtures/send.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | } 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/model_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from smsapi.models import ResultCollection, SendResult 6 | from tests import SmsApiTestCase 7 | 8 | 9 | class ModelTest(SmsApiTestCase): 10 | 11 | def test_iterate_over_results(self): 12 | model1 = SendResult(id=1, points=0.1) 13 | model2 = SendResult(id=2, points=0.2) 14 | 15 | r = ResultCollection(2, [model1, model2]) 16 | 17 | self.assertEqual(model1, r.next()) 18 | self.assertEqual(model2, r.next()) 19 | 20 | 21 | def suite(): 22 | suite = unittest.TestSuite() 23 | suite.addTest(unittest.makeSuite(ModelTest)) 24 | return suite -------------------------------------------------------------------------------- /tests/unit/sender/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/sender/__init__.py -------------------------------------------------------------------------------- /tests/unit/sender/api_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from smsapi.models import ResultCollection 4 | from smsapi.sender.models import SenderNameResult, SenderNameSuccessResult 5 | from tests import SmsApiTestCase 6 | from tests.unit.doubles import api_response_fixture 7 | 8 | 9 | class SenderApiTest(SmsApiTestCase): 10 | 11 | @api_response_fixture('success_response') 12 | def test_add_sender_name(self): 13 | name = 'any' 14 | result = self.client.sender.add(name=name) 15 | 16 | expected_result = SenderNameSuccessResult(1) 17 | 18 | self.assertRequestMethodIsPost() 19 | self.assertEqual(expected_result, result) 20 | self.assertRequestPayloadContains('add', name) 21 | 22 | @api_response_fixture('check_sender_name') 23 | def test_check_sender_name(self): 24 | name = 'any' 25 | result = self.client.sender.check(name=name) 26 | 27 | expected_result = create_sender_name_result(name) 28 | 29 | self.assertRequestMethodIsPost() 30 | self.assertEqual(expected_result, result) 31 | self.assertRequestPayloadContains('status', name) 32 | 33 | @api_response_fixture('success_response') 34 | def test_remove_sender_name(self): 35 | name = 'any' 36 | 37 | result = self.client.sender.remove(name=name) 38 | 39 | expected_result = SenderNameSuccessResult(1) 40 | 41 | self.assertRequestMethodIsPost() 42 | self.assertEqual(expected_result, result) 43 | self.assertRequestPayloadContains('delete', name) 44 | 45 | def test_list_sender_names(self): 46 | result = self.client.sender.list() 47 | 48 | expected_result = ResultCollection(2, [ 49 | create_sender_name_result('any'), 50 | create_sender_name_result('some'), 51 | ]) 52 | 53 | self.assertEqual(expected_result, result) 54 | self.assertParamsForwardedToRequestEquals({'list': True}) 55 | 56 | @api_response_fixture('success_response') 57 | def test_set_default_sender_name(self): 58 | name = 'any' 59 | 60 | result = self.client.sender.default(name=name) 61 | 62 | expected_result = SenderNameSuccessResult(1) 63 | 64 | self.assertRequestMethodIsPost() 65 | self.assertEqual(expected_result, result) 66 | self.assertRequestPayloadContains('default', name) 67 | 68 | 69 | def create_sender_name_result(name): 70 | return SenderNameResult(name, 'ACTIVE', False) 71 | 72 | 73 | def suite(): 74 | s = unittest.TestSuite() 75 | s.addTest(unittest.makeSuite(SenderApiTest)) 76 | return s 77 | -------------------------------------------------------------------------------- /tests/unit/sender/fixtures/check_sender_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "sender": "any", 5 | "status": "ACTIVE", 6 | "default": false 7 | } 8 | } -------------------------------------------------------------------------------- /tests/unit/sender/fixtures/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": [ 4 | { 5 | "sender": "any", 6 | "status": "ACTIVE", 7 | "default": false 8 | }, 9 | { 10 | "sender": "some", 11 | "status": "ACTIVE", 12 | "default": false 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /tests/unit/sender/fixtures/success_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1 5 | } 6 | } -------------------------------------------------------------------------------- /tests/unit/short_url/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/short_url/__init__.py -------------------------------------------------------------------------------- /tests/unit/short_url/api_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import unittest 5 | 6 | from smsapi.models import ModelCollection 7 | from smsapi.short_url.models import ShortUrlClick, ShortUrlClickMessage, ShortUrlClickByMobileDevice, ShortUrl 8 | from smsapi.utils import dict_replace 9 | from tests import SmsApiTestCase 10 | from tests.unit.doubles import api_response_fixture 11 | 12 | 13 | def create_short_url_click(): 14 | 15 | message = ShortUrlClickMessage(id='1', idx='1', recipient="100200300") 16 | 17 | return ShortUrlClick( 18 | idzdo_id="1", 19 | phone_number="100200300", 20 | date_hit="2015-09-20T17:00:00+0200", 21 | name="name1", 22 | short_url="http:/idz.do/name1", 23 | os="Windows", 24 | browser="Firefox", 25 | device="PC", 26 | suffix="test1", 27 | message=message) 28 | 29 | 30 | def create_short_url_click_by_mobile_device(): 31 | 32 | return ShortUrlClickByMobileDevice(link_id=1, android=0, ios=0, wp=0, other=1, sum=1) 33 | 34 | 35 | def create_short_url_link(): 36 | 37 | return ShortUrl( 38 | id="1", 39 | name="test1", 40 | url="http://invoid.pl", 41 | short_url="http://idz.do/test1", 42 | type="URL", 43 | expire="2037-12-02T15:14:36+00:00", 44 | hits=0, 45 | hits_unique=0, 46 | description="test1") 47 | 48 | 49 | def create_collection(data): 50 | return ModelCollection(len(data), data) 51 | 52 | 53 | class ShortUrlApiTest(SmsApiTestCase): 54 | 55 | def test_get_clicks(self): 56 | args = {'date_from': 'some date', 'date_to': 'another date', 'links': ['1', '2']} 57 | 58 | r = self.client.shorturl.get_clicks(**args) 59 | 60 | expected_result = create_collection([create_short_url_click()]) 61 | expected_args = dict_replace(args, 'links', {'links[]': ['1', '2']}) 62 | 63 | self.assertEqual(expected_result, r) 64 | self.assertEqual(expected_args, self.request_fake.params) 65 | 66 | def test_get_clicks_by_mobile_device(self): 67 | args = {'links': ['1']} 68 | 69 | r = self.client.shorturl.get_clicks_by_mobile_device(**args) 70 | 71 | expected_result = create_collection([create_short_url_click_by_mobile_device()]) 72 | 73 | self.assertEqual(expected_result, r) 74 | self.assertEqual(dict_replace(args, 'links', {'links[]': ['1']}), self.request_fake.params) 75 | 76 | def test_list_short_urls(self): 77 | r = self.client.shorturl.list_short_urls() 78 | 79 | expected_result = create_collection([create_short_url_link()]) 80 | 81 | self.assertEqual(expected_result, r) 82 | 83 | @api_response_fixture('short_url') 84 | def test_create_short_url(self): 85 | 86 | args = {'url': 'some url', 'name': 'name', 'expire': 'some expiration datetime', 'description': 'description'} 87 | 88 | r = self.client.shorturl.create_short_url(**args) 89 | 90 | expected_result = create_short_url_link() 91 | 92 | self.assertEqual(expected_result, r) 93 | 94 | @api_response_fixture('short_url') 95 | def test_create_short_url_with_file(self): 96 | path = os.path.join(os.path.dirname(__file__), 'fixtures/some_file') 97 | 98 | args = {'url': 'some url', 'file': path, 'name': 'name'} 99 | 100 | self.client.shorturl.create_short_url(**args) 101 | 102 | self.assertEqual(open(args.get('file'), 'rb').read(), self.request_fake.files.get('file').read()) 103 | 104 | @api_response_fixture('short_url') 105 | def test_get_short_url(self): 106 | id = '1' 107 | 108 | r = self.client.shorturl.get_short_url(id=id) 109 | 110 | expected_result = create_short_url_link() 111 | 112 | self.assertEqual(expected_result, r) 113 | self.assertTrue(self.request_fake.url.endswith('short_url/links/%s' % id)) 114 | 115 | @api_response_fixture('short_url') 116 | def test_update_short_url(self): 117 | id = '1' 118 | 119 | args = {'id': id, 'url': 'some url', 'name': 'name'} 120 | 121 | r = self.client.shorturl.update_short_url(**args) 122 | 123 | expected_result = create_short_url_link() 124 | 125 | self.assertEqual(expected_result, r) 126 | self.assertEqual('PUT', self.request_fake.http_method) 127 | self.assertTrue(self.request_fake.url.endswith('short_url/links/%s' % id)) 128 | 129 | def test_remove_short_url(self): 130 | id = '1' 131 | 132 | self.client.shorturl.remove_short_url(id=id) 133 | 134 | self.assertEqual('DELETE', self.request_fake.http_method) 135 | self.assertTrue(self.request_fake.url.endswith('short_url/links/%s' % id)) 136 | 137 | 138 | def suite(): 139 | suite = unittest.TestSuite() 140 | suite.addTest(unittest.makeSuite(ShortUrlApiTest)) 141 | return suite 142 | -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/get_clicks.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "idz_do_id": "1", 8 | "phone_number": "100200300", 9 | "date_hit": "2015-09-20T17:00:00+0200", 10 | "name": "name1", 11 | "short_url": "http:/idz.do/name1", 12 | "os": "Windows", 13 | "browser": "Firefox", 14 | "device": "PC", 15 | "suffix": "test1", 16 | "message": { 17 | "id": "1", 18 | "idx": "1", 19 | "recipient": "100200300" 20 | } 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/get_clicks_by_mobile_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response":{ 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "link_id": 1, 8 | "clicks": { 9 | "android": 0, 10 | "ios": 0, 11 | "wp": 0, 12 | "other": 1, 13 | "sum": 1 14 | } 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/list_short_urls.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "size": 1, 5 | "collection": [ 6 | { 7 | "id": "1", 8 | "name": "test1", 9 | "url": "http://invoid.pl", 10 | "short_url": "http://idz.do/test1", 11 | "filename": null, 12 | "type": "URL", 13 | "expire": "2037-12-02T15:14:36+00:00", 14 | "hits": 0, 15 | "hits_unique": 0, 16 | "description": "test1" 17 | } 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/remove_short_url.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 204 3 | } -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/short_url.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "id": "1", 5 | "name": "test1", 6 | "url": "http://invoid.pl", 7 | "domain": null, 8 | "short_url": "http://idz.do/test1", 9 | "filename": null, 10 | "type": "URL", 11 | "expire": "2037-12-02T15:14:36+00:00", 12 | "hits": 0, 13 | "hits_unique": 0, 14 | "description": "test1" 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/short_url/fixtures/some_file: -------------------------------------------------------------------------------- 1 | some file -------------------------------------------------------------------------------- /tests/unit/sms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/sms/__init__.py -------------------------------------------------------------------------------- /tests/unit/sms/api_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from smsapi.exception import EndpointException, SendException 6 | from smsapi.models import ResultCollection, RemoveMessageResult, InvalidNumber 7 | from smsapi.sms.api import flash_force_params, fast_force_params 8 | from tests import SmsApiTestCase 9 | from tests.unit.doubles import api_response_fixture 10 | 11 | 12 | class SmsApiTest(SmsApiTestCase): 13 | 14 | def test_send_sms(self): 15 | number = '48100200300' 16 | kwargs = { 17 | 'to': number, 18 | 'from': 'test' 19 | } 20 | 21 | result = self.client.sms.send(**kwargs) 22 | 23 | self.assertSmsSendResultForNumberEquals(number, result) 24 | self.assertParamsForwardedToRequestEquals(kwargs) 25 | 26 | @api_response_fixture('send') 27 | def test_send_sms_with_custom_sender(self): 28 | number = '48100200300' 29 | any_sender_name = 'any-sender-name' 30 | 31 | result = self.client.sms.send(to=number, from_=any_sender_name) 32 | 33 | self.assertSmsSendResultForNumberEquals(number, result) 34 | self.assertParamsForwardedToRequestEquals({'to': number, 'from': any_sender_name}) 35 | 36 | @api_response_fixture('send_to_many_recipients') 37 | def test_send_sms_to_many_numbers(self): 38 | number_1, number_2 = '48100200300', '48100200301' 39 | 40 | result = self.client.sms.send(to=[number_1, number_2]) 41 | 42 | self.assertSmsSendResultForNumberEquals([number_1, number_2], result) 43 | self.assertParamsForwardedToRequestEquals({'to': '%s,%s' % (number_1, number_2)}) 44 | 45 | @api_response_fixture('send_to_invalid_number') 46 | def test_send_sms_to_invalid_number(self): 47 | 48 | invalid_number = '48100200300' 49 | exception = None 50 | 51 | try: 52 | self.client.sms.send(to=invalid_number) 53 | except SendException as e: 54 | exception = e 55 | 56 | expected_exception = create_sms_exception_for_number(invalid_number) 57 | 58 | self.assertEqual(expected_exception, exception) 59 | 60 | def test_send_fast(self): 61 | number = '48100200300' 62 | args = {'to': number} 63 | 64 | result = self.client.sms.send_fast(**args) 65 | 66 | self.assertRequestMethodIsPost() 67 | self.assertSmsSendResultForNumberEquals(number, result) 68 | self.assertParamsForwardedToRequestEquals(args, fast_force_params) 69 | 70 | def test_send_flash(self): 71 | number = '48100200300' 72 | args = {'to': number} 73 | 74 | result = self.client.sms.send_flash(**args) 75 | 76 | self.assertRequestMethodIsPost() 77 | self.assertSmsSendResultForNumberEquals(number, result) 78 | self.assertParamsForwardedToRequestEquals(args, flash_force_params) 79 | 80 | def test_remove_scheduled_sms(self): 81 | sms_id = '1' 82 | 83 | result = self.client.sms.remove_scheduled(id=sms_id) 84 | 85 | expected_result = ResultCollection(1, [RemoveMessageResult(id='1')]) 86 | 87 | self.assertRequestMethodIsPost() 88 | self.assertRequestPayloadContains("sch_del", "1") 89 | self.assertEqual(expected_result, result) 90 | 91 | @api_response_fixture('remove_not_exists_sms') 92 | def test_remove_not_exists_sms(self): 93 | exception = None 94 | 95 | try: 96 | self.client.sms.remove_scheduled(id='1') 97 | except EndpointException as e: 98 | exception = e 99 | 100 | expected_exception = EndpointException(u'Not exists ID message', 301) 101 | 102 | self.assertEqual(expected_exception, exception) 103 | 104 | @api_response_fixture('send') 105 | def test_send_personalized_sms(self): 106 | args = {'to': '48100200300', 'message': 'some message [%1]', 'param1': ['p1', 'p2']} 107 | 108 | self.client.sms.send(**args) 109 | 110 | self.assertParamsForwardedToRequestEquals(args, {'param1': 'p1|p2'}) 111 | 112 | @api_response_fixture('send') 113 | def test_send_sms_with_own_identifier(self): 114 | args = {'to': '48100200300', 'idx': ['id1', 'id2']} 115 | 116 | self.client.sms.send(**args) 117 | 118 | self.assertParamsForwardedToRequestEquals(args, {'idx': 'id1|id2'}) 119 | 120 | @api_response_fixture('send') 121 | def test_send_sms_to_group(self): 122 | self.client.sms.send_to_group(group='any') 123 | 124 | self.assertParamsForwardedToRequestEquals({'group': 'any'}) 125 | 126 | def test_send_sms_as_utf8(self): 127 | number = '48100200300' 128 | args = {'to': number, 'encoding': 'utf-8'} 129 | 130 | result = self.client.sms.send(**args) 131 | 132 | self.assertSmsSendResultForNumberEquals(number, result) 133 | self.assertParamsForwardedToRequestEquals(args) 134 | 135 | def test_send_test_sms(self): 136 | number = '48100200300' 137 | args = {'to': number, 'test': '1'} 138 | 139 | self.client.sms.send(**args) 140 | 141 | self.assertParamsForwardedToRequestEquals(args) 142 | 143 | @api_response_fixture('detailed_response') 144 | def test_send_sms_with_detailed_response(self): 145 | result = self.client.sms.send(details=1) 146 | 147 | self.assertEqual(4, result.length) 148 | self.assertEqual(1, result.parts) 149 | self.assertEqual("test", result.message) 150 | self.assertRequestPayloadContains("details", "1") 151 | 152 | 153 | def create_sms_exception_for_number(number): 154 | e = SendException(u'No correct phone numbers', 13) 155 | e.add_invalid_number(InvalidNumber(number, number, u'Invalid phone number')) 156 | return e 157 | 158 | 159 | def suite(): 160 | suite = unittest.TestSuite() 161 | suite.addTest(unittest.makeSuite(SmsApiTest)) 162 | return suite 163 | -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/detailed_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "message": "test", 6 | "length": 4, 7 | "parts": 1, 8 | "list": [ 9 | { 10 | "id": "1" 11 | } 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/remove_not_exists_sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "error": 301, 5 | "message": "Not exists ID message" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/remove_scheduled.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1" 8 | } 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/send.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | } 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/send_fast.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | } 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/send_flash.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | } 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/send_to_invalid_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "response": { 4 | "invalid_numbers": [ 5 | { 6 | "number": "48100200300", 7 | "submitted_number": "48100200300", 8 | "message": "Invalid phone number" 9 | } 10 | ], 11 | "error": 13, 12 | "message": "No correct phone numbers" 13 | } 14 | } -------------------------------------------------------------------------------- /tests/unit/sms/fixtures/send_to_many_recipients.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 2, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | }, 14 | { 15 | "id": "1", 16 | "points": 0.1, 17 | "number": "48100200301", 18 | "date_sent": 1460969712, 19 | "submitted_number": "48100200301", 20 | "status": "QUEUE" 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tests/unit/utils_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import datetime 3 | 4 | from smsapi.utils import convert_iso8601_str_to_datetime, FixedOffset 5 | 6 | 7 | class UtilsTest(unittest.TestCase): 8 | 9 | def test_convert_iso8601_string_with_Z_utc_offset_info_to_datetime(self): 10 | iso8601_str = "2060-01-01T20:00:00Z" 11 | 12 | r = convert_iso8601_str_to_datetime(iso8601_str) 13 | 14 | self.assertEqual(datetime.datetime(2060, 1, 1, 20, 0, 0, tzinfo=FixedOffset()), r) 15 | 16 | def test_convert_iso8601_string_without_utc_offset_to_datetime(self): 17 | iso8601_str = "2060-01-01T20:00:00" 18 | 19 | r = convert_iso8601_str_to_datetime(iso8601_str) 20 | 21 | self.assertEqual(datetime.datetime(2060, 1, 1, 20, 0, 0, tzinfo=FixedOffset()), r) 22 | 23 | def test_convert_iso8601_string_with_colon_separated_utc_offset_info_to_datetime(self): 24 | iso8601_str = "2060-01-01T20:00:00+02:04" 25 | 26 | r = convert_iso8601_str_to_datetime(iso8601_str) 27 | 28 | expected_datetime = datetime.datetime(2060, 1, 1, 20, 0, 0, 29 | tzinfo=FixedOffset(offset_hours=2, offset_minutes=4)) 30 | self.assertEqual(expected_datetime, r) 31 | 32 | def test_convert_iso8601_string_with_utc_offset_info_to_datetime(self): 33 | iso8601_str = "2060-01-01T20:00:00+0204" 34 | 35 | r = convert_iso8601_str_to_datetime(iso8601_str) 36 | 37 | expected_datetime = datetime.datetime(2060, 1, 1, 20, 0, 0, 38 | tzinfo=FixedOffset(offset_hours=2, offset_minutes=4)) 39 | self.assertEqual(expected_datetime, r) 40 | 41 | 42 | def suite(): 43 | s = unittest.TestSuite() 44 | s.addTest(unittest.makeSuite(UtilsTest)) 45 | return s 46 | -------------------------------------------------------------------------------- /tests/unit/vms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/vms/__init__.py -------------------------------------------------------------------------------- /tests/unit/vms/api_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from smsapi.exception import EndpointException 5 | from smsapi.models import ResultCollection, RemoveMessageResult 6 | from tests import SmsApiTestCase, create_send_result 7 | from tests.unit.doubles import api_response_fixture 8 | 9 | 10 | class VmsApiTest(SmsApiTestCase): 11 | 12 | def test_send_vms(self): 13 | number = '48100200300' 14 | args = {'to': number} 15 | 16 | result = self.client.vms.send(**args) 17 | 18 | self.assertParamsForwardedToRequestEquals(args) 19 | self.assertSendResultForNumberEquals(number, result) 20 | 21 | def test_send_vms_from_file(self): 22 | 23 | wave_file_path = os.path.join(os.path.dirname(__file__), 'fixtures/example.wav') 24 | 25 | args = {'to': '48100200300', 'file': wave_file_path} 26 | 27 | self.client.vms.send(**args) 28 | 29 | self.assertEqual(open(wave_file_path, 'rb').read(), self.request_fake.files.get('file').read()) 30 | 31 | def test_send_vms_from_remote_file(self): 32 | args = {'to': '48100200300', 'file': 'http://somedomain.com/somefile.wav'} 33 | 34 | self.client.vms.send(**args) 35 | 36 | self.assertParamsForwardedToRequestEquals(args) 37 | 38 | def test_send_vms_with_own_identifiers(self): 39 | number = '48100200300' 40 | args = {'to': number, 'idx': ['id1', 'id2']} 41 | 42 | result = self.client.vms.send(**args) 43 | 44 | self.assertParamsForwardedToRequestEquals(args, {'idx': 'id1|id2'}) 45 | self.assertSendResultForNumberEquals(number, result) 46 | 47 | @api_response_fixture('send') 48 | def test_send_vms_to_group(self): 49 | args = {'group': 'any'} 50 | 51 | result = self.client.vms.send_to_group(**args) 52 | 53 | expected_result = ResultCollection(1, [create_send_result('48100200300')]) 54 | 55 | self.assertParamsForwardedToRequestEquals(args) 56 | self.assertEqual(expected_result, result) 57 | 58 | def test_remove_scheduled_vms(self): 59 | vms_id = '1' 60 | 61 | result = self.client.vms.remove_scheduled(id=vms_id) 62 | 63 | expected_result = ResultCollection(1, [RemoveMessageResult(id=vms_id)]) 64 | 65 | self.assertRequestMethodIsPost() 66 | self.assertEqual(expected_result, result) 67 | self.assertRequestPayloadContains("sch_del", "1") 68 | 69 | @api_response_fixture('remove_not_exists_vms') 70 | def test_remove_not_exists_vms(self): 71 | exception = None 72 | 73 | try: 74 | self.client.vms.remove_scheduled(id='1') 75 | except EndpointException as e: 76 | exception = e 77 | 78 | expected_exception = EndpointException(u'Not exists ID message', 301) 79 | 80 | self.assertEqual(expected_exception, exception) 81 | 82 | def test_send_test_vms(self): 83 | number = '48100200300' 84 | args = {'to': number, 'test': '1'} 85 | 86 | self.client.vms.send(**args) 87 | 88 | self.assertParamsForwardedToRequestEquals(args) 89 | 90 | def test_send_test_vms_with_try_parameter(self): 91 | self.client.vms.send(try_=3) 92 | 93 | self.assertParamsForwardedToRequestEquals({'try': '3'}) 94 | 95 | 96 | def suite(): 97 | s = unittest.TestSuite() 98 | s.addTest(unittest.makeSuite(VmsApiTest)) 99 | return s 100 | -------------------------------------------------------------------------------- /tests/unit/vms/fixtures/example.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-python-client/9af0e67c0a7936bcfc873e8249368bca3032c55c/tests/unit/vms/fixtures/example.wav -------------------------------------------------------------------------------- /tests/unit/vms/fixtures/remove_not_exists_vms.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "error": 301, 5 | "message": "Not exists ID message" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/unit/vms/fixtures/remove_scheduled.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1" 8 | } 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /tests/unit/vms/fixtures/send.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "response": { 4 | "count": 1, 5 | "list": [ 6 | { 7 | "id": "1", 8 | "points": 0.1, 9 | "number": "48100200300", 10 | "date_sent": 1460969712, 11 | "submitted_number": "48100200300", 12 | "status": "QUEUE" 13 | } 14 | ] 15 | } 16 | } --------------------------------------------------------------------------------