Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2841e59
Add first 2FA endpoint and tests
strosek Oct 28, 2022
2423172
Add whatsapp test case and config from dotenv files
strosek Nov 1, 2022
fad1666
Add create 2FA app endpoint and test
strosek Dec 5, 2022
91228cc
Add 2FA message template endpoints
strosek Dec 7, 2022
7546afc
Add send PIN over sms endpoint and models
strosek Dec 9, 2022
ddca1d9
Fix type inheritance for responses/bodies
strosek Dec 9, 2022
c1ef5b9
Add send PIN over voice and verification endpoints
strosek Dec 12, 2022
fa52bdc
Add live and model tests for 2FA, model fixes
strosek Jan 18, 2023
866534c
Apply black formatting
strosek Jan 18, 2023
b3b5c7f
Fix SendPIN* response object structure
strosek Jan 24, 2023
dfef934
Cleanup unneeded Optional import
strosek Jan 24, 2023
f61a77f
Improvements in send PIN models and tests
strosek Jan 24, 2023
6345f4a
Add integration tests for TFA applications
strosek Jan 25, 2023
97e4a91
Add live tests for PIN verification
strosek Jan 26, 2023
e06ddbe
Apply formatting to tests
strosek Jan 26, 2023
663b0e8
Add verify phone number validations and tests
strosek Jan 27, 2023
5f86c94
Bump minor version
strosek Feb 1, 2023
2ded16b
Add integration tests for TFA apps and templates
strosek Feb 1, 2023
8997a0d
Apply black formatting to tests
strosek Feb 1, 2023
32b5b2e
Add all integration tests for 2FA
strosek Feb 3, 2023
1659ff8
Add user-agent header to track SDK usage
strosek Feb 3, 2023
4455b31
Fix name in library import name for user-agent header
strosek Feb 3, 2023
a525f8a
Add actions for Python 3.11 and update pre-commit hooks rev
strosek Feb 3, 2023
75c0b67
Update isort and black pre-commits
strosek Feb 3, 2023
313cdaf
Add specific black format target
strosek Feb 3, 2023
7294ab4
Remove 3.7 build for success in isort
strosek Feb 3, 2023
488679a
Fix pre-commit issues
strosek Feb 3, 2023
52cd2d4
Add missing test for get_tfa_message_templates
strosek Feb 3, 2023
939cb40
Add dotenv dependency
strosek Feb 3, 2023
3b14a90
Lower minimum coverage to 98%
strosek Feb 7, 2023
b0150e0
Add test for get_tfa_message_template, fix models
strosek Feb 7, 2023
3ae4792
Remove py3.7, use env_list
strosek Feb 7, 2023
06c721c
Move ignored, live samples to samples directory
strosek Feb 7, 2023
ee31ce2
Move ignored, WA live samples to samples directory
strosek Feb 7, 2023
2eb675c
Ignore whatsapp sample test
strosek Feb 7, 2023
c542795
Add py311 to Tox
strosek Feb 7, 2023
aea1022
Remove python311 tests
strosek Feb 8, 2023
493768a
Update and enhance Readme and Contributing files
strosek Feb 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@v2
Expand Down
9 changes: 4 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.4.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 23.1.0
hooks:
- id: black
args:
- "--line-length=88"
args: ["--line-length", "88", "--target-version", "py39"]
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
Expand Down
43 changes: 42 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ git fetch upstream
### 🛠️ Step 2: Build
Please run all tests that are in repository, all test should pass.
Please check do you need to activate some additional features that are repository or langauge specific.
For example in infobip-api-python-sdk, pre-commit hooks must be enabled. [Python Readme](README.md)
For example in infobip-api-python-sdk, pre-commit hooks must be enabled. See Commit section below.

### 🌱 Step 3: Branch
To keep your development environment organized, create local branches to hold your work.
Expand All @@ -66,6 +66,41 @@ Most important things to keep in mind are:
* 88 character line limits rather than 79. (differ from PEP-8)

### ✅ Step 5: Commit

#### Enable pre-commit hooks (recommended)
To enable pre-commit hooks run:
```bash
pip install -r requirements/dev.txt
```
You will need to install pre-commit hooks
Using homebrew:
```bash
brew install pre-commit
```
Using conda (via conda-forge):
```bash
conda install -c conda-forge pre-commit
```
To check installation run:
```bash
pre-commit --version
```
If installation was successful you will see version number.
You can find the Pre-commit configuration in `.pre-commit-config.yaml`.
Install the git hook scripts:
```bash
pre-commit install
```
Run against all files:
```bash
pre-commit run --all-files
```
If setup was successful pre-commit will run on every commit.
Every time you clone a project that uses pre-commit, running `pre-commit install`
should be the first thing you do.

#### Commit changes

It is recommended to keep your changes grouped logically within individual commits.
Many contributors find it easier to review changes that are split across multiple commits.
There is no limit to the number of commits in a pull request.
Expand Down Expand Up @@ -99,6 +134,12 @@ Bug fixes and features should always come with tests. Looking at other tests to
Before submitting your changes in a pull request, always run the full test suite.
Make sure the linter does not report any issues and that all tests pass. Please do not submit patches that fail either check.

To run tests position yourself in the project's root while your virtual environment
is active and run:
```bash
python -m pytest
```

### 🚀 Step 8: Push
Once your commits are ready to go -- with passing tests and linting -- begin the process of opening a pull request by pushing your working branch to your fork on GitHub.
```bash
Expand Down
86 changes: 33 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
[![Licence](https://img.shields.io/github/license/infobip-community/infobip-api-python-sdk)](LICENSE)
![Lines](https://img.shields.io/tokei/lines/github/infobip-community/infobip-api-python-sdk)

Python client for Infobip's API channels.
Client SDK to use the Infobip API with Python.

This package enables you to use multiple Infobip communication channels, like SMS, MMS, WhatsApp, Email, etc.

---

## 📡 Supported channels
- [SMS Reference](https://www.infobip.com/docs/api#channels/sms)

- [SMS + 2FA Reference](https://www.infobip.com/docs/api#channels/sms)
- [Whatsapp Reference](https://www.infobip.com/docs/api#channels/whatsapp)
- [Email Reference](https://www.infobip.com/docs/api#channels/email)
- [WebRTC Reference](https://www.infobip.com/docs/api#channels/webrtc/)
Expand All @@ -21,13 +24,6 @@ Python client for Infobip's API channels.

More channels to be added in the near future.

## ℹ️ General Info

For `infobip-api-python-sdk` versioning we use
[Semantic Versioning](https://semver.org) scheme.

Python 3.6 is minimum supported version by this library.

## 🔐 Authentication

Currently, infobip-api-python-sdk only supports API Key authentication,
Expand All @@ -36,14 +32,15 @@ This will most likely change with future versions,
once more authentication methods are included.

## 📦 Installation

To install infobip SDK you will need to run:

```bash
pip install infobip-api-python-sdk
```

Details of the package can be found
[here](https://pypi.org/project/infobip-api-python-sdk/)
in the [PyPI page](https://pypi.org/project/infobip-api-python-sdk/).

## 🚀 Usage

Expand All @@ -52,9 +49,9 @@ To use the package you'll need an Infobip account.
If you don't already have one, you can create a free trial account
[here](https://www.infobip.com/signup).

In this example we will show how to send WhatsApp text message.
Similar can be done for other channels.
First step is to import necessary channel, in this case WhatsApp channel.
In this example, we will show how to send a WhatsApp text message.
Other channels can be used in a similar way.
The first step is to import the necessary channel, in this case WhatsApp channel.

```python
from infobip_channels.whatsapp.channel import WhatsAppChannel
Expand All @@ -68,6 +65,14 @@ c = WhatsAppChannel.from_auth_params({
"api_key": "<your_api_key>"
})
```

Alternatively, you can create the instance from the environment, having the `IB_BASE_URL` and `IB_API_KEY` variables
set, like this:

```python
c = WhatsAppChannel.from_env()
```

After that you can access all the methods from `WhatsAppChannel`.
To send text message you can use `send_text_message` method and add correct payload:
```python
Expand All @@ -84,48 +89,23 @@ response = c.send_text_message(
}
)
```
## 🧪 Testing
To run tests position yourself in the project's root while your virtual environment
is active and run:
```bash
python -m pytest
```

## ✅ Enable pre-commit hooks
To enable pre-commit hooks run:
```bash
pip install -r requirements/dev.txt
```
You will need to install pre-commit hooks
Using homebrew:
```bash
brew install pre-commit
```
Using conda (via conda-forge):
```bash
conda install -c conda-forge pre-commit
```
To check installation run:
```bash
pre-commit --version
```
If installation was successful you will see version number.
You can find the Pre-commit configuration in `.pre-commit-config.yaml`.
Install the git hook scripts:
```bash
pre-commit install
```
Run against all files:
```bash
pre-commit run --all-files
```
If setup was successful pre-commit will run on every commit.
Every time you clone a project that uses pre-commit, running `pre-commit install`
should be the first thing you do.
### Samples

## ⚖️ License
We are adding samples in the [samples](samples) folder, which you can use as a reference on how to use the SDK
with real payloads.

This library is distributed under the MIT license found in the [License](LICENSE).
## 🗒️ Notes

For `infobip-api-python-sdk` versioning we use
[Semantic Versioning](https://semver.org) scheme.

Python 3.6 is the minimum supported version by this library.

## 🧡 Want to help and improve this open-source SDK?

## 🆘 Want to help and improve open source SDK?
Check out our [contributing guide](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md).

## ⚖️ License

This library is distributed under the MIT license found in the [License](LICENSE).
32 changes: 31 additions & 1 deletion infobip_channels/core/channel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
from abc import ABC, abstractmethod
from typing import Any, Dict, Type, Union

import requests
from dotenv import load_dotenv
from pydantic import AnyHttpUrl, BaseModel, ValidationError

from infobip_channels.core.http_client import _HttpClient
Expand Down Expand Up @@ -38,13 +40,41 @@ class for making HTTP requests.
client = _HttpClient(Authentication(**auth_params))
return cls(client)

@classmethod
def from_env(cls) -> "Channel":
"""Create an Authentication instance from the environment and
use it to instantiate the Channel subclass. The IB_API_KEY and IB_BASE_URL environment variables must be set.
The Channel subclass instantiated this way will use the default _HttpClient
class for making HTTP requests.

:return: Instance of the subclass
"""
base_url = os.environ.get("IB_BASE_URL")
api_key = os.environ.get("IB_API_KEY")

auth_params = {"base_url": base_url, "api_key": api_key}
client = _HttpClient(Authentication(**auth_params))
return cls(client)

@classmethod
def from_dotenv(cls) -> "Channel":
"""Create an Authentication instance from a dotenv file and
use it to instantiate the Channel subclass. The IB_API_KEY and IB_BASE_URL environment variables must present.
The Channel subclass instantiated this way will use the default _HttpClient
class for making HTTP requests.

:return: Instance of the subclass
"""
load_dotenv()
return cls.from_env()

@classmethod
def from_auth_instance(cls, auth_instance: Authentication) -> "Channel":
"""Instantiate the Channel subclass with the provided auth object.
The Channel subclass instantiated this way will use the default _HttpClient
class for making HTTP requests.

:param auth_instance: Authentication class instance
:param: auth_instance: Authentication class instance
:return: Instance of the subclass
"""
client = _HttpClient(auth_instance)
Expand Down
4 changes: 4 additions & 0 deletions infobip_channels/core/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ def post(
endpoint: str,
body: Union[Dict, bytes] = None,
headers: RequestHeaders = None,
params: Dict = None,
) -> requests.Response:
"""Send an HTTP post request to base_url + endpoint.

:param endpoint: Which endpoint to hit
:param body: Body to send with the request
:param headers: Request headers
:param params: Additional parameters
:return: Received response
"""
headers = headers or self.post_headers
Expand All @@ -57,6 +59,8 @@ def post(
else:
kwargs = {"data": body}

kwargs.setdefault("params", params)

return requests.post(url=url, headers=headers.dict(by_alias=True), **kwargs)

def get(
Expand Down
17 changes: 16 additions & 1 deletion infobip_channels/core/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import json
import os
import sys
import urllib.parse
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone
from enum import Enum
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Tuple, Union

import pkg_resources
import requests
from pydantic import AnyHttpUrl, BaseModel, constr, validator
from urllib3 import encode_multipart_formdata
Expand All @@ -21,6 +23,16 @@ def to_header_specific_case(string: str) -> str:
return "-".join(word.capitalize() for word in string.split("_"))


def get_package_version() -> str:
sdk_version = ""
if "infobip_channels" in sys.modules:
sdk_version = (
"/" + pkg_resources.get_distribution("infobip-api-python-sdk").version
)

return sdk_version


def url_encoding(string_to_encode: str, safe: str = "", encoding: str = "utf-8") -> str:
"""
Special characters and user credentials are properly encoded.
Expand Down Expand Up @@ -118,7 +130,6 @@ def convert_time_to_correct_format(cls, value) -> str:

@classmethod
def convert_time_to_correct_format_validate_limit(cls, value):

date_time_format = cls.convert_to_date_time_format(value)
date_time_limit = datetime.now(timezone.utc) + timedelta(days=cls._TIME_LIMIT)
if date_time_format > date_time_limit:
Expand Down Expand Up @@ -160,6 +171,10 @@ class RequestHeaders(BaseModel):
authorization: str
accept: Optional[str] = "application/json"

user_agent: str = (
f"@infobip/python-sdk{get_package_version()} python/{sys.version.split(' ')[0]}"
)

class Config:
alias_generator = to_header_specific_case
allow_population_by_field_name = True
Expand Down
3 changes: 1 addition & 2 deletions infobip_channels/email/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,8 @@ def _get_custom_response_class(
raw_response: Union[requests.Response, Any],
response_class: Type[ResponseBase] = SendEmailResponse,
*args,
**kwargs
**kwargs,
) -> Type[ResponseBase]:

if raw_response.status_code == HTTPStatus.OK:
return response_class
elif raw_response.status_code in (
Expand Down
2 changes: 1 addition & 1 deletion infobip_channels/mms/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _get_custom_response_class(
raw_response: Union[requests.Response, Any],
response_class: Type[ResponseBase] = SendMMSResponse,
*args,
**kwargs
**kwargs,
) -> Type[ResponseBase]:
if raw_response.status_code in (
HTTPStatus.OK,
Expand Down
2 changes: 1 addition & 1 deletion infobip_channels/rcs/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def _get_custom_response_class(
raw_response: Union[requests.Response, Any],
response_ok_class: Type[ResponseBase] = RCSResponseOK,
*args,
**kwargs
**kwargs,
) -> Type[ResponseBase]:
if raw_response.status_code == HTTPStatus.OK:
return response_ok_class
Expand Down
Loading