Skip to content

Commit

Permalink
Added HTTP source
Browse files Browse the repository at this point in the history
  • Loading branch information
bastantoine committed Oct 19, 2024
1 parent fe56880 commit 9ba9671
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 10 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,45 @@ If a param is provided both in the config file and in the environement, the valu
> [!TIP]
> The paths in `files` can either be absolute, relative to the current user homedir, or relative the current working directory.
#### HTTP call

```json
{
"type": "http",
"name": "http",
"url": "https://httpbin.org/json",
"method": "GET",
"request_params": {
"headers": {
"Authorization": "Bearer THIS_IS_A_TOKEN"
}
}
}
```

| Key | Description | Required | Default value |
| ----------------- | ------------------------------------------------ | -------- | ------------- |
| `type` | Type of source | Yes | `"http"` |
| `name` | Name of source | Yes | |
| `url` | URL to call | Yes |   |
| `method` | HTTP method to use to do the API call | Yes | |
| `request_params` | Any extra param required for the call. | No | |

> [!TIP]
> The `request_params` is provided as kwargs to the [`requests.request`](https://requests.readthedocs.io/en/latest/api/#requests.request). Check out the documentation to see which parameter is available.
> [!TIP]
> The archive created after the backup is a JSON with the following format:
> ```json
> {
> "url": "<the URL called>",
> "timestamp": "<backup timestamp>",
> "result": "<API call status code>",
> "detail": "<API response when JSON>",
> "msg": "<API response body when not JSON>"
> }
> ```
### Available destinations
#### S3
Expand Down
4 changes: 4 additions & 0 deletions backup_me/sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@
from .base import BaseSource
from .db import MySQLDB, PostgresDB
from .files import ArchiveType, RawFiles
from .http import HTTP


class SourceTypes(str, Enum):
mysql = "mysql"
postgres = "postgres"
files = "files"
http = "http"


SOURCES: t.Dict[SourceTypes, BaseSource] = {
SourceTypes.mysql: MySQLDB,
SourceTypes.postgres: PostgresDB,
SourceTypes.files: RawFiles,
SourceTypes.http: HTTP,
}

__all__ = [
Expand All @@ -25,4 +28,5 @@ class SourceTypes(str, Enum):
"RawFiles",
"MySQLDB",
"PostgresDB",
"HTTP",
]
42 changes: 42 additions & 0 deletions backup_me/sources/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import json
import os
import typing as t

import requests

from .base import BaseSource


class HTTP(BaseSource):
url: str
method: str
request_params: t.Dict[str, t.Any]

def backup(self, output_dir: str) -> str:
resp = requests.request(self.method, self.url, **self.request_params)

result = {
"url": self.url,
"timestamp": self.now(),
"result": resp.status_code,
}
is_json = resp.headers.get("Content-Type", "").lower() == "application/json"
if is_json:
result["detail"] = resp.json()
else:
result["msg"] = resp.text

backup_filename = (
f"{os.path.join(output_dir, self.backup_filename)}_{self.now()}.json"
)
with open(backup_filename, "w") as f:
json.dump(result, f)

if resp.ok:
print(f"HTTP call for backup completed successfully.")
else:
print(
f"HTTP call for backup failed with return code {resp.status_code} {resp.text}."
)

return backup_filename
36 changes: 27 additions & 9 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ python = "^3.11"
boto3 = "^1.34.14"
typer = { extras = ["all"], version = "^0.9.0" }
pydantic = "^2.6.1"
requests = "^2.32.3"

[tool.poetry.group.tests.dependencies]
pytest = "^8.0.0"
coverage = "^7.4.1"
pytest-subprocess = "^1.5.0"
pytest-mock = "^3.12.0"
moto = { extras = ["s3"], version = "^5.0.9" }
requests-mock = "^1.12.1"

[tool.poetry.scripts]
backup-me = 'backup_me.main:console'
Expand Down
54 changes: 53 additions & 1 deletion tests/test_sources.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import json
import os
import tarfile
import zipfile
from datetime import datetime

from backup_me.sources import ArchiveType, MySQLDB, PostgresDB, RawFiles
import pytest

from backup_me.sources import HTTP, ArchiveType, MySQLDB, PostgresDB, RawFiles


def test_rawfiles_source(tmp_path):
Expand Down Expand Up @@ -114,3 +117,52 @@ def test_postgres_source(tmp_path, fake_process, mocker):
fake_process.register(cmd, stdout="")
backup = postgres_source.backup(tmp_path)
assert fake_process.call_count(cmd) == 2


@pytest.mark.parametrize(
"status,json_body,text_body",
[
(200, {"status": "OK", "msg": "Success"}, ""),
(200, {}, "Sucess"),
(500, {"status": "KO", "msg": "Error"}, ""),
(500, {}, "Error"),
],
)
def test_http_source(status, json_body, text_body, tmp_path, requests_mock, mocker):
now = datetime.now()
mock_date = mocker.patch("backup_me.sources.base.datetime")
mock_date.now.return_value = now

url = "https://backup.me"
args = {"status_code": status}
if json_body:
args["json"] = json_body
args["headers"] = {"Content-Type": "application/json"}
else:
args["text"] = text_body
requests_mock.post(url, **args)

backup_filename = "backup"
source = HTTP(
backup_filename=backup_filename,
name="http",
url=url,
method="POST",
request_params={"headers": {"Authorization": "Bearer XYZ"}},
)
filename = f"{os.path.join(tmp_path, backup_filename)}_{source.now()}.json"
backup = source.backup(tmp_path)
assert backup == filename
with open(backup) as f:
result = json.load(f)
expected = {
"url": url,
"timestamp": source.now(),
"result": status,
}
if json_body:
expected["detail"] = json_body
else:
expected["msg"] = text_body

assert result == expected

0 comments on commit 9ba9671

Please sign in to comment.