Skip to content

Commit 37c9104

Browse files
authored
Merge pull request #76 from infobip-community/add-tfa
Add 2FA channel support.
2 parents 7f19902 + 493768a commit 37c9104

File tree

92 files changed

+3239
-105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+3239
-105
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ['3.7', '3.8', '3.9', '3.10']
18+
python-version: ['3.8', '3.9', '3.10']
1919

2020
steps:
2121
- uses: actions/checkout@v2

.pre-commit-config.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.1.0
3+
rev: v4.4.0
44
hooks:
55
- id: check-yaml
66
- id: end-of-file-fixer
77
- id: trailing-whitespace
88
- repo: https://github.com/pycqa/isort
9-
rev: 5.10.1
9+
rev: 5.12.0
1010
hooks:
1111
- id: isort
1212
args: ["--profile", "black"]
1313
- repo: https://github.com/psf/black
14-
rev: 22.3.0
14+
rev: 23.1.0
1515
hooks:
1616
- id: black
17-
args:
18-
- "--line-length=88"
17+
args: ["--line-length", "88", "--target-version", "py39"]
1918
- repo: https://github.com/pycqa/flake8
2019
rev: 4.0.1
2120
hooks:

CONTRIBUTING.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ git fetch upstream
4444
### 🛠️ Step 2: Build
4545
Please run all tests that are in repository, all test should pass.
4646
Please check do you need to activate some additional features that are repository or langauge specific.
47-
For example in infobip-api-python-sdk, pre-commit hooks must be enabled. [Python Readme](README.md)
47+
For example in infobip-api-python-sdk, pre-commit hooks must be enabled. See Commit section below.
4848

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

6868
### ✅ Step 5: Commit
69+
70+
#### Enable pre-commit hooks (recommended)
71+
To enable pre-commit hooks run:
72+
```bash
73+
pip install -r requirements/dev.txt
74+
```
75+
You will need to install pre-commit hooks
76+
Using homebrew:
77+
```bash
78+
brew install pre-commit
79+
```
80+
Using conda (via conda-forge):
81+
```bash
82+
conda install -c conda-forge pre-commit
83+
```
84+
To check installation run:
85+
```bash
86+
pre-commit --version
87+
```
88+
If installation was successful you will see version number.
89+
You can find the Pre-commit configuration in `.pre-commit-config.yaml`.
90+
Install the git hook scripts:
91+
```bash
92+
pre-commit install
93+
```
94+
Run against all files:
95+
```bash
96+
pre-commit run --all-files
97+
```
98+
If setup was successful pre-commit will run on every commit.
99+
Every time you clone a project that uses pre-commit, running `pre-commit install`
100+
should be the first thing you do.
101+
102+
#### Commit changes
103+
69104
It is recommended to keep your changes grouped logically within individual commits.
70105
Many contributors find it easier to review changes that are split across multiple commits.
71106
There is no limit to the number of commits in a pull request.
@@ -99,6 +134,12 @@ Bug fixes and features should always come with tests. Looking at other tests to
99134
Before submitting your changes in a pull request, always run the full test suite.
100135
Make sure the linter does not report any issues and that all tests pass. Please do not submit patches that fail either check.
101136

137+
To run tests position yourself in the project's root while your virtual environment
138+
is active and run:
139+
```bash
140+
python -m pytest
141+
```
142+
102143
### 🚀 Step 8: Push
103144
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.
104145
```bash

README.md

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
[![Licence](https://img.shields.io/github/license/infobip-community/infobip-api-python-sdk)](LICENSE)
88
![Lines](https://img.shields.io/tokei/lines/github/infobip-community/infobip-api-python-sdk)
99

10-
Python client for Infobip's API channels.
10+
Client SDK to use the Infobip API with Python.
11+
12+
This package enables you to use multiple Infobip communication channels, like SMS, MMS, WhatsApp, Email, etc.
1113

1214
---
1315

1416
## 📡 Supported channels
15-
- [SMS Reference](https://www.infobip.com/docs/api#channels/sms)
17+
18+
- [SMS + 2FA Reference](https://www.infobip.com/docs/api#channels/sms)
1619
- [Whatsapp Reference](https://www.infobip.com/docs/api#channels/whatsapp)
1720
- [Email Reference](https://www.infobip.com/docs/api#channels/email)
1821
- [WebRTC Reference](https://www.infobip.com/docs/api#channels/webrtc/)
@@ -21,13 +24,6 @@ Python client for Infobip's API channels.
2124

2225
More channels to be added in the near future.
2326

24-
## ℹ️ General Info
25-
26-
For `infobip-api-python-sdk` versioning we use
27-
[Semantic Versioning](https://semver.org) scheme.
28-
29-
Python 3.6 is minimum supported version by this library.
30-
3127
## 🔐 Authentication
3228

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

3834
## 📦 Installation
35+
3936
To install infobip SDK you will need to run:
4037

4138
```bash
4239
pip install infobip-api-python-sdk
4340
```
4441

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

4845
## 🚀 Usage
4946

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

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

5956
```python
6057
from infobip_channels.whatsapp.channel import WhatsAppChannel
@@ -68,6 +65,14 @@ c = WhatsAppChannel.from_auth_params({
6865
"api_key": "<your_api_key>"
6966
})
7067
```
68+
69+
Alternatively, you can create the instance from the environment, having the `IB_BASE_URL` and `IB_API_KEY` variables
70+
set, like this:
71+
72+
```python
73+
c = WhatsAppChannel.from_env()
74+
```
75+
7176
After that you can access all the methods from `WhatsAppChannel`.
7277
To send text message you can use `send_text_message` method and add correct payload:
7378
```python
@@ -84,48 +89,23 @@ response = c.send_text_message(
8489
}
8590
)
8691
```
87-
## 🧪 Testing
88-
To run tests position yourself in the project's root while your virtual environment
89-
is active and run:
90-
```bash
91-
python -m pytest
92-
```
9392

94-
## ✅ Enable pre-commit hooks
95-
To enable pre-commit hooks run:
96-
```bash
97-
pip install -r requirements/dev.txt
98-
```
99-
You will need to install pre-commit hooks
100-
Using homebrew:
101-
```bash
102-
brew install pre-commit
103-
```
104-
Using conda (via conda-forge):
105-
```bash
106-
conda install -c conda-forge pre-commit
107-
```
108-
To check installation run:
109-
```bash
110-
pre-commit --version
111-
```
112-
If installation was successful you will see version number.
113-
You can find the Pre-commit configuration in `.pre-commit-config.yaml`.
114-
Install the git hook scripts:
115-
```bash
116-
pre-commit install
117-
```
118-
Run against all files:
119-
```bash
120-
pre-commit run --all-files
121-
```
122-
If setup was successful pre-commit will run on every commit.
123-
Every time you clone a project that uses pre-commit, running `pre-commit install`
124-
should be the first thing you do.
93+
### Samples
12594

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

128-
This library is distributed under the MIT license found in the [License](LICENSE).
98+
## 🗒️ Notes
99+
100+
For `infobip-api-python-sdk` versioning we use
101+
[Semantic Versioning](https://semver.org) scheme.
102+
103+
Python 3.6 is the minimum supported version by this library.
104+
105+
## 🧡 Want to help and improve this open-source SDK?
129106

130-
## 🆘 Want to help and improve open source SDK?
131107
Check out our [contributing guide](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md).
108+
109+
## ⚖️ License
110+
111+
This library is distributed under the MIT license found in the [License](LICENSE).

infobip_channels/core/channel.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import os
12
from abc import ABC, abstractmethod
23
from typing import Any, Dict, Type, Union
34

45
import requests
6+
from dotenv import load_dotenv
57
from pydantic import AnyHttpUrl, BaseModel, ValidationError
68

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

43+
@classmethod
44+
def from_env(cls) -> "Channel":
45+
"""Create an Authentication instance from the environment and
46+
use it to instantiate the Channel subclass. The IB_API_KEY and IB_BASE_URL environment variables must be set.
47+
The Channel subclass instantiated this way will use the default _HttpClient
48+
class for making HTTP requests.
49+
50+
:return: Instance of the subclass
51+
"""
52+
base_url = os.environ.get("IB_BASE_URL")
53+
api_key = os.environ.get("IB_API_KEY")
54+
55+
auth_params = {"base_url": base_url, "api_key": api_key}
56+
client = _HttpClient(Authentication(**auth_params))
57+
return cls(client)
58+
59+
@classmethod
60+
def from_dotenv(cls) -> "Channel":
61+
"""Create an Authentication instance from a dotenv file and
62+
use it to instantiate the Channel subclass. The IB_API_KEY and IB_BASE_URL environment variables must present.
63+
The Channel subclass instantiated this way will use the default _HttpClient
64+
class for making HTTP requests.
65+
66+
:return: Instance of the subclass
67+
"""
68+
load_dotenv()
69+
return cls.from_env()
70+
4171
@classmethod
4272
def from_auth_instance(cls, auth_instance: Authentication) -> "Channel":
4373
"""Instantiate the Channel subclass with the provided auth object.
4474
The Channel subclass instantiated this way will use the default _HttpClient
4575
class for making HTTP requests.
4676
47-
:param auth_instance: Authentication class instance
77+
:param: auth_instance: Authentication class instance
4878
:return: Instance of the subclass
4979
"""
5080
client = _HttpClient(auth_instance)

infobip_channels/core/http_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ def post(
4141
endpoint: str,
4242
body: Union[Dict, bytes] = None,
4343
headers: RequestHeaders = None,
44+
params: Dict = None,
4445
) -> requests.Response:
4546
"""Send an HTTP post request to base_url + endpoint.
4647
4748
:param endpoint: Which endpoint to hit
4849
:param body: Body to send with the request
4950
:param headers: Request headers
51+
:param params: Additional parameters
5052
:return: Received response
5153
"""
5254
headers = headers or self.post_headers
@@ -57,6 +59,8 @@ def post(
5759
else:
5860
kwargs = {"data": body}
5961

62+
kwargs.setdefault("params", params)
63+
6064
return requests.post(url=url, headers=headers.dict(by_alias=True), **kwargs)
6165

6266
def get(

infobip_channels/core/models.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import json
22
import os
3+
import sys
34
import urllib.parse
45
import xml.etree.ElementTree as ET
56
from datetime import datetime, timedelta, timezone
67
from enum import Enum
78
from http import HTTPStatus
89
from typing import Any, Dict, List, Optional, Tuple, Union
910

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

2325

26+
def get_package_version() -> str:
27+
sdk_version = ""
28+
if "infobip_channels" in sys.modules:
29+
sdk_version = (
30+
"/" + pkg_resources.get_distribution("infobip-api-python-sdk").version
31+
)
32+
33+
return sdk_version
34+
35+
2436
def url_encoding(string_to_encode: str, safe: str = "", encoding: str = "utf-8") -> str:
2537
"""
2638
Special characters and user credentials are properly encoded.
@@ -118,7 +130,6 @@ def convert_time_to_correct_format(cls, value) -> str:
118130

119131
@classmethod
120132
def convert_time_to_correct_format_validate_limit(cls, value):
121-
122133
date_time_format = cls.convert_to_date_time_format(value)
123134
date_time_limit = datetime.now(timezone.utc) + timedelta(days=cls._TIME_LIMIT)
124135
if date_time_format > date_time_limit:
@@ -160,6 +171,10 @@ class RequestHeaders(BaseModel):
160171
authorization: str
161172
accept: Optional[str] = "application/json"
162173

174+
user_agent: str = (
175+
f"@infobip/python-sdk{get_package_version()} python/{sys.version.split(' ')[0]}"
176+
)
177+
163178
class Config:
164179
alias_generator = to_header_specific_case
165180
allow_population_by_field_name = True

infobip_channels/email/channel.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ def _get_custom_response_class(
9797
raw_response: Union[requests.Response, Any],
9898
response_class: Type[ResponseBase] = SendEmailResponse,
9999
*args,
100-
**kwargs
100+
**kwargs,
101101
) -> Type[ResponseBase]:
102-
103102
if raw_response.status_code == HTTPStatus.OK:
104103
return response_class
105104
elif raw_response.status_code in (

infobip_channels/mms/channel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _get_custom_response_class(
3131
raw_response: Union[requests.Response, Any],
3232
response_class: Type[ResponseBase] = SendMMSResponse,
3333
*args,
34-
**kwargs
34+
**kwargs,
3535
) -> Type[ResponseBase]:
3636
if raw_response.status_code in (
3737
HTTPStatus.OK,

infobip_channels/rcs/channel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def _get_custom_response_class(
2424
raw_response: Union[requests.Response, Any],
2525
response_ok_class: Type[ResponseBase] = RCSResponseOK,
2626
*args,
27-
**kwargs
27+
**kwargs,
2828
) -> Type[ResponseBase]:
2929
if raw_response.status_code == HTTPStatus.OK:
3030
return response_ok_class

0 commit comments

Comments
 (0)