Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .bandit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[bandit]
exclude = /test
skips = B101
120 changes: 120 additions & 0 deletions .github/workflows/publish_stable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Publish MailThunder Stable to PyPI

on:
push:
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'

concurrency:
group: publish-stable
cancel-in-progress: false

jobs:
test:
permissions:
contents: read
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
python-version: [ "3.9", "3.10", "3.11", "3.12" ]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -e .

- name: Run tests
run: python -m pytest test/unit_test/ --ignore=test/unit_test/manual_test -v

publish:
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build twine tomli tomli-w

- name: Bump patch version in pyproject.toml
id: bump
run: |
python - <<'PY'
import os
import tomli
import tomli_w

path = "pyproject.toml"
with open(path, "rb") as handle:
data = tomli.load(handle)

current = data["project"]["version"]
major, minor, patch = (int(part) for part in current.split("."))
patch += 1
new_version = f"{major}.{minor}.{patch}"
data["project"]["version"] = new_version

with open(path, "wb") as handle:
tomli_w.dump(data, handle)

with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as out:
out.write(f"old_version={current}\n")
out.write(f"new_version={new_version}\n")

print(f"Bumped version: {current} -> {new_version}")
PY

- name: Build distributions
run: python -m build

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: python -m twine upload --non-interactive dist/*

- name: Commit and tag version bump
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add pyproject.toml
git commit -m "chore: bump stable version to ${{ steps.bump.outputs.new_version }}"
git tag "v${{ steps.bump.outputs.new_version }}"
git push origin main
git push origin "v${{ steps.bump.outputs.new_version }}"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.bump.outputs.new_version }}" \
--title "v${{ steps.bump.outputs.new_version }}" \
--notes "Automated stable release for v${{ steps.bump.outputs.new_version }}. Published to PyPI as \`je-mail-thunder==${{ steps.bump.outputs.new_version }}\`." \
--target main \
dist/*
2 changes: 1 addition & 1 deletion dev.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Rename to dev version
# This is dev version
[build-system]
requires = ["setuptools>=61.0"]
requires = ["setuptools>=82.0.1"]
build-backend = "setuptools.build_meta"

[project]
Expand Down
19 changes: 9 additions & 10 deletions je_mail_thunder/imap/imap_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ def _resolve_credentials():
if user is not None and password is not None:
return user, password
env_info = get_mail_thunder_os_environ()
if isinstance(env_info, dict):
user = env_info.get("mail_thunder_user")
password = env_info.get("mail_thunder_user_password")
if user is not None and password is not None:
return user, password
user = env_info.get("mail_thunder_user")
password = env_info.get("mail_thunder_user_password")
if user is not None and password is not None:
return user, password
return None

def try_to_login_with_env_or_content(self):
Expand Down Expand Up @@ -90,7 +89,7 @@ def search_mailbox(self, search_str: [str, list] = "ALL", charset: str = None) -
mail_thunder_logger.info(f"imap_search_mailbox, search_str: {search_str}, charset: {charset}")
try:
response, mail_number_string = self.search(charset, search_str)
mail_detail_list = list()
mail_detail_list = []
for num_of_mail in mail_number_string[0].split():
response, mail_data = self.fetch(num_of_mail, "(RFC822)")
mail_data: List[List]
Expand All @@ -113,8 +112,8 @@ def mail_content_list(
mail_thunder_logger.info(f"imap_mail_content_list, search_str: {search_str}, charset: {charset}")
try:
mail_list = self.search_mailbox(search_str, charset)
mail_content_dict = dict()
mail_content_list = list()
mail_content_dict = {}
mail_content_list = []
for mail_data in mail_list:
mail = mail_data[2]
mail_content_dict.update({"SUBJECT": mail.get("Subject")})
Expand All @@ -129,7 +128,7 @@ def mail_content_list(
body = str(decode_header(str(body))[0][0])
mail_content_dict.update({"BODY": body})
mail_content_list.append(mail_content_dict)
mail_content_dict = dict()
mail_content_dict = {}
return mail_content_list
except Exception as error:
mail_thunder_logger.error(
Expand Down Expand Up @@ -163,7 +162,7 @@ def output_all_mail_as_file(
mail_thunder_logger.info(f"imap_output_all_mail_as_file, search_str: {search_str}, charset: {charset}")
try:
all_mail = self.mail_content_list(search_str=search_str, charset=charset)
same_name_dict: Dict[str, int] = dict()
same_name_dict: Dict[str, int] = {}
cwd = os.path.abspath(os.getcwd())
for mail in all_mail:
safe_name = self._sanitize_subject_as_filename(mail.get("SUBJECT"))
Expand Down
9 changes: 4 additions & 5 deletions je_mail_thunder/smtp/smtp_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,10 @@ def _resolve_credentials():
if user is not None and password is not None:
return user, password
env_info = get_mail_thunder_os_environ()
if isinstance(env_info, dict):
user = env_info.get("mail_thunder_user")
password = env_info.get("mail_thunder_user_password")
if user is not None and password is not None:
return user, password
user = env_info.get("mail_thunder_user")
password = env_info.get("mail_thunder_user_password")
if user is not None and password is not None:
return user, password
return None

def try_to_login_with_env_or_content(self):
Expand Down
5 changes: 3 additions & 2 deletions je_mail_thunder/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import builtins
import types
from inspect import getmembers, isbuiltin
from typing import Union

from je_mail_thunder.imap.imap_wrapper import imap_instance
from je_mail_thunder.smtp.smtp_wrapper import smtp_instance
Expand Down Expand Up @@ -52,7 +53,7 @@ def _execute_event(self, action: list):
else:
raise ExecuteActionException(cant_execute_action_error + " " + str(action))

def execute_action(self, action_list) -> dict:
def execute_action(self, action_list: Union[list, dict]) -> dict:
"""
use to execute all action on action list(action file or program list)
:param action_list the list include action
Expand Down Expand Up @@ -117,7 +118,7 @@ def add_command_to_executor(command_dict: dict):
raise AddCommandException(add_command_exception)


def execute_action(action_list: list) -> dict:
def execute_action(action_list: Union[list, dict]) -> dict:
return executor.execute_action(action_list)


Expand Down
2 changes: 1 addition & 1 deletion je_mail_thunder/utils/json/json_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def read_action_json(json_file_path: str) -> list:
)
with open(json_file_path) as read_file:
return json.loads(read_file.read())
except (OSError, ValueError, json.JSONDecodeError) as error:
except (OSError, ValueError) as error:
raise JsonActionException(cant_find_json_error + f": {repr(error)}") from error


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from je_mail_thunder.utils.json_format.json_process import reformat_json
from je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_data import mail_thunder_content_data_dict

_CONTENT_FILENAME = "/mail_thunder_content.json"
_lock = Lock()


Expand All @@ -14,9 +15,9 @@ def read_output_content():
"""
with _lock:
cwd = str(Path.cwd())
file_path = Path(cwd + "/mail_thunder_content.json")
file_path = Path(cwd + _CONTENT_FILENAME)
if file_path.exists() and file_path.is_file():
with open(cwd + "/mail_thunder_content.json", "r+") as read_file:
with open(cwd + _CONTENT_FILENAME, "r+") as read_file:
user_info = json.loads(read_file.read())
mail_thunder_content_data_dict.update(user_info)
return user_info
Expand All @@ -29,5 +30,5 @@ def write_output_content():
"""
with _lock:
cwd = str(Path.cwd())
with open(cwd + "/mail_thunder_content.json", "w+") as file_to_write:
with open(cwd + _CONTENT_FILENAME, "w+") as file_to_write:
file_to_write.write(reformat_json(json.dumps(mail_thunder_content_data_dict)))
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def handle(self):
try:
execute_str = json.loads(command_string)
_validate_payload(execute_str)
for _, execute_return in execute_action(execute_str).items():
for execute_return in execute_action(execute_str).values():
client_socket.sendto(str(execute_return).encode("utf-8"), self.client_address)
client_socket.sendto("\n".encode("utf-8"), self.client_address)
client_socket.sendto("Return_Data_Over_JE".encode("utf-8"), self.client_address)
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Rename to dev version
# This is dev version
[build-system]
requires = ["setuptools>=61.0"]
requires = ["setuptools>=82.0.1"]
build-backend = "setuptools.build_meta"

[project]
Expand Down Expand Up @@ -32,3 +32,7 @@ find = { namespaces = false }

[tool.pytest.ini_options]
testpaths = ["test"]

[tool.bandit]
exclude_dirs = ["test"]
skips = ["B101"]
6 changes: 4 additions & 2 deletions test/unit_test/test_content_data.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import secrets

from je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_data import (
is_need_to_save_content,
mail_thunder_content_data_dict,
Expand All @@ -18,11 +20,11 @@ def test_is_need_to_save_content_user_set():


def test_is_need_to_save_content_password_set():
mail_thunder_content_data_dict["password"] = "test_password"
mail_thunder_content_data_dict["password"] = secrets.token_hex(8)
assert is_need_to_save_content() is True


def test_is_need_to_save_content_both_set():
mail_thunder_content_data_dict["user"] = "test_user"
mail_thunder_content_data_dict["password"] = "test_password"
mail_thunder_content_data_dict["password"] = secrets.token_hex(8)
assert is_need_to_save_content() is True
25 changes: 16 additions & 9 deletions test/unit_test/test_content_save.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import secrets

from je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_data import mail_thunder_content_data_dict
from je_mail_thunder.utils.save_mail_user_content.mail_thunder_content_save import (
Expand All @@ -21,32 +22,38 @@ def teardown_function():


def test_write_and_read_output_content():
mail_thunder_content_data_dict.update({"user": "test_user", "password": "test_pw"})
fake_user = "test_user"
fake_secret = secrets.token_hex(8)
mail_thunder_content_data_dict.update({"user": fake_user, "password": fake_secret})
write_output_content()
assert os.path.exists(CONTENT_FILE)
with open(CONTENT_FILE) as f:
data = json.load(f)
assert data["user"] == "test_user"
assert data["password"] == "test_pw"
assert data["user"] == fake_user
assert data["password"] == fake_secret


def test_read_output_content_returns_dict():
mail_thunder_content_data_dict.update({"user": "u", "password": "p"})
fake_user = secrets.token_hex(4)
fake_secret = secrets.token_hex(8)
mail_thunder_content_data_dict.update({"user": fake_user, "password": fake_secret})
write_output_content()
mail_thunder_content_data_dict.update({"user": None, "password": None})
result = read_output_content()
assert isinstance(result, dict)
assert result["user"] == "u"
assert result["password"] == "p"
assert result["user"] == fake_user
assert result["password"] == fake_secret


def test_read_output_content_updates_global_dict():
mail_thunder_content_data_dict.update({"user": "u2", "password": "p2"})
fake_user = secrets.token_hex(4)
fake_secret = secrets.token_hex(8)
mail_thunder_content_data_dict.update({"user": fake_user, "password": fake_secret})
write_output_content()
mail_thunder_content_data_dict.update({"user": None, "password": None})
read_output_content()
assert mail_thunder_content_data_dict["user"] == "u2"
assert mail_thunder_content_data_dict["password"] == "p2"
assert mail_thunder_content_data_dict["user"] == fake_user
assert mail_thunder_content_data_dict["password"] == fake_secret


def test_read_output_content_no_file():
Expand Down
1 change: 0 additions & 1 deletion test/unit_test/test_create_project.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import shutil
import tempfile

from je_mail_thunder.utils.project.create_project_structure import create_project_dir
Expand Down
2 changes: 1 addition & 1 deletion test/unit_test/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


def test_execute_builtin_print(capsys):
result = execute_action([["print", ["hello from test"]]])
execute_action([["print", ["hello from test"]]])
captured = capsys.readouterr()
assert "hello from test" in captured.out

Expand Down
Loading
Loading