Skip to content

Commit 0f841f0

Browse files
authored
Merge pull request #10 from getslash/updates
Updates
2 parents 4ef75fd + 76a39cb commit 0f841f0

11 files changed

Lines changed: 255 additions & 113 deletions

File tree

.github/workflows/main.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
# manually triggered
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
15+
env:
16+
UV_PYTHON: ${{ matrix.python-version }}
17+
18+
steps:
19+
- name: Checkout the repository
20+
uses: actions/checkout@main
21+
- name: Install the default version of uv
22+
id: setup-uv
23+
uses: astral-sh/setup-uv@v3
24+
- name: Print the installed version
25+
run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}"
26+
- name: Install Python ${{ matrix.python-version }}
27+
run: uv python install ${{ matrix.python-version }}
28+
29+
- name: Tests
30+
run: |
31+
uv venv
32+
uv pip install ".[testing]"
33+
.venv/bin/pytest tests
34+
.venv/bin/pylint --rcfile=.pylintrc mailboxer tests
35+
36+
publish:
37+
if: startsWith(github.ref, 'refs/tags/')
38+
needs: test
39+
runs-on: ubuntu-latest
40+
environment: release
41+
permissions:
42+
id-token: write
43+
44+
steps:
45+
- name: Checkout repository
46+
uses: actions/checkout@v4
47+
with:
48+
fetch-depth: 0
49+
50+
- name: Set up Python
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: "3.11"
54+
55+
- name: Install hatch
56+
run: pip install hatch
57+
58+
- name: Build package
59+
run: hatch build
60+
61+
- name: Publish to PyPI
62+
uses: pypa/gh-action-pypi-publish@release/v1
63+
with:
64+
attestations: true
65+
skip-existing: true

.pylintrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[MESSAGES CONTROL]
2+
disable=missing-docstring,too-many-public-methods,too-few-public-methods,no-self-use,import-error,locally-disabled,invalid-name,wrong-import-order,,bad-option-value,ungrouped-imports,too-many-arguments,useless-object-inheritance,consider-using-f-string,unnecessary-lambda-assignment,raise-missing-from,unspecified-encoding
3+
4+
[FORMAT]
5+
max-line-length=120
6+
7+
[REPORTS]
8+
reports=no
9+
score=no
10+
output-format=parseable

Makefile

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
default: test
22

33
test: env
4-
.env/bin/pytest -x tests
4+
.venv/bin/pytest -x tests
55

6-
travis_test: env
7-
PYTHONPATH=.env/mailboxer .env/bin/pytest
8-
9-
env: .env/.up-to-date
10-
11-
12-
.env/.up-to-date: setup.py Makefile
13-
python -m virtualenv .env
14-
.env/bin/pip install -e ".[testing]"
15-
touch $@
6+
env:
7+
uv venv
8+
uv pip install -e ".[testing]"
169

mailboxer/mailboxer.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
from .query import Query
66

77

8-
class Mailboxer(object):
9-
8+
class Mailboxer:
109
def __init__(self, url):
11-
super(Mailboxer, self).__init__()
10+
super().__init__()
1211
self.url = URL(url).add_path("v2")
1312

1413
def create_mailbox(self, address):
@@ -18,7 +17,7 @@ def create_mailbox(self, address):
1817
def delete_mailbox(self, address):
1918
return self.get_mailbox(address).delete()
2019

21-
def get_emails(self, address, unread = False):
20+
def get_emails(self, address, unread=False):
2221
return self.get_mailbox(address).get_emails(unread)
2322

2423
def get_mailboxes(self, **kwargs):
@@ -31,48 +30,56 @@ def does_mailbox_exist(self, address):
3130
return Mailbox(self, address).exists()
3231

3332
def _post(self, url, data):
34-
returned = requests.post(url, data=json.dumps(data),
35-
headers={"Content-type": "application/json"})
33+
returned = requests.post(
34+
url,
35+
data=json.dumps(data),
36+
headers={"Content-type": "application/json"},
37+
timeout=30,
38+
)
3639
returned.raise_for_status()
3740
return returned
3841

3942
def _get_paged(self, url, obj):
40-
response = requests.get(url)
43+
response = requests.get(url, timeout=30)
4144
response.raise_for_status()
4245
return [obj(data) for data in response.json()["result"]]
4346

4447
def _mailbox_url(self, address):
4548
return self.url.add_path("mailboxes").add_path(address)
4649

47-
class Mailbox(object):
4850

51+
class Mailbox:
4952
def __init__(self, mailboxer, address):
50-
super(Mailbox, self).__init__()
53+
super().__init__()
5154
self.mailboxer = mailboxer
5255
self.address = address
5356
self.url = self.mailboxer.url.add_path("mailboxes").add_path(self.address)
5457

5558
@classmethod
56-
def from_query_json(cls, mailboxer, json):
59+
def from_query_json(cls, mailboxer, json): # pylint: disable=redefined-outer-name
5760
return cls(mailboxer, json["address"])
5861

59-
def get_emails(self, unread = False):
60-
url = self.url.add_path("unread_emails") if unread else self.url.add_path("emails")
61-
return self.mailboxer._get_paged(url, Email)
62+
def get_emails(self, unread=False):
63+
url = (
64+
self.url.add_path("unread_emails")
65+
if unread
66+
else self.url.add_path("emails")
67+
)
68+
return self.mailboxer._get_paged(url, Email) # pylint: disable=protected-access
6269

6370
def exists(self):
6471
url = self.url.add_path("emails")
65-
response = requests.get(url)
66-
if(response.status_code == requests.codes.not_found):
72+
response = requests.get(url, timeout=30)
73+
if response.status_code == requests.codes.not_found: # pylint: disable=no-member
6774
return False
6875
response.raise_for_status()
6976
return True
7077

7178
def delete(self):
72-
requests.delete(self.url).raise_for_status()
79+
requests.delete(self.url, timeout=30).raise_for_status()
7380

74-
class Email(object):
7581

82+
class Email:
7683
def __init__(self, email_dict):
77-
super(Email, self).__init__()
84+
super().__init__()
7885
self.__dict__.update(email_dict)

mailboxer/query.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import requests
22

3-
class Query(object):
43

4+
class Query:
55
_metadata = None
66

77
def __init__(self, client, url, objtype, page_size=100):
8-
super(Query, self).__init__()
8+
super().__init__()
99
self.client = client
1010
self.url = url.set_query_param("page_size", str(page_size))
1111
self.objtype = objtype
@@ -14,7 +14,9 @@ def __init__(self, client, url, objtype, page_size=100):
1414

1515
def _fetch_page(self, page_index):
1616
assert page_index > 0
17-
result = requests.get(self.url.set_query_param("page", str(page_index)))
17+
result = requests.get(
18+
self.url.set_query_param("page", str(page_index)), timeout=30
19+
)
1820
result.raise_for_status()
1921
result_json = result.json()
2022
if self._objects is None:
@@ -28,7 +30,7 @@ def __iter__(self):
2830

2931
def get_json_objects(self):
3032
for i in range(len(self)):
31-
obj = self._objects[i]
33+
assert self._objects is not None
3234
if self._objects[i] is None:
3335
self._fetch_page((i // self.page_size) + 1)
3436
assert self._objects[i] is not None
@@ -37,4 +39,5 @@ def get_json_objects(self):
3739
def __len__(self):
3840
if self._objects is None:
3941
self._fetch_page(1)
42+
assert self._objects is not None
4043
return len(self._objects)

pyproject.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[build-system]
2+
requires = ["hatchling>=0.25.1", "hatch-vcs"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "mailboxer-python"
7+
description = "Client library for Mailboxer"
8+
readme = "README.md"
9+
requires-python = ">=3.8"
10+
license = { text = "BSD 3-Clause License" }
11+
12+
classifiers = [
13+
"Programming Language :: Python :: 3.8",
14+
"Programming Language :: Python :: 3.9",
15+
"Programming Language :: Python :: 3.10",
16+
"Programming Language :: Python :: 3.11",
17+
"Programming Language :: Python :: 3.12",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
20+
]
21+
22+
dependencies = ["requests", "URLObject"]
23+
dynamic = ["version"]
24+
25+
authors = [{ name = "Rotem Yaari", email = "vmalloc@gmail.com" }]
26+
27+
[project.urls]
28+
"Homepage" = "https://github.com/getslash/mailboxer-python"
29+
30+
[project.optional-dependencies]
31+
testing = ["dataclasses_json", "Flask-Loopback", "pylint", "pytest"]
32+
33+
[tool.hatch.build.targets.wheel]
34+
packages = ["mailboxer"]
35+
36+
[tool.hatch.version]
37+
source = "vcs"

requirements.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

setup.cfg

Lines changed: 0 additions & 22 deletions
This file was deleted.

setup.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/conftest.py

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import os
2-
import shutil
3-
import subprocess
4-
import sys
5-
import tempfile
61
import uuid
72

83
from urlobject import URLObject as URL
@@ -11,63 +6,42 @@
116
import pytest
127
from mailboxer import Mailboxer
138

14-
sys.path.insert(0, os.path.join(
15-
os.path.abspath(os.path.dirname(__file__)),
16-
"..", ".env", "mailboxer"))
17-
from flask_app.app import create_app
18-
from flask_app import models
9+
from .flask_app import create_app, app_initializations
1910

20-
def pytest_addoption(parser):
21-
parser.addoption("--setup-db", action="store_true", default=False)
2211

23-
@pytest.fixture(scope="session")
24-
def db_engine(request):
25-
if request.config.getoption("--setup-db"):
26-
tmpdir = tempfile.mkdtemp()
27-
subprocess.check_call("pg_ctl init -D {0} -w".format(tmpdir), shell=True)
28-
subprocess.check_call("pg_ctl start -D {0} -w".format(tmpdir), shell=True)
29-
30-
@request.addfinalizer
31-
def finalize():
32-
subprocess.check_call("pg_ctl stop -D {0} -w -m immediate".format(tmpdir), shell=True)
33-
shutil.rmtree(tmpdir)
34-
35-
subprocess.check_call("createdb mailboxer", shell=True)
36-
37-
with create_app().app_context():
38-
models.db.session.close()
39-
models.db.drop_all()
40-
models.db.create_all()
41-
42-
43-
@pytest.fixture(scope="session")
44-
def mailboxer_url(request, db_engine):
45-
loopback = FlaskLoopback(create_app())
12+
@pytest.fixture(scope="session", name="mailboxer_url")
13+
def mailboxer_url_fx():
14+
app = create_app()
15+
loopback = FlaskLoopback(app)
4616
hostname = str(uuid.uuid1())
47-
loopback.activate_address((hostname, 80))
48-
@request.addfinalizer
49-
def close():
50-
loopback.deactivate_address((hostname, 80))
51-
return URL("http://{0}".format(hostname))
17+
port = 80
18+
loopback.activate_address((hostname, port))
19+
with app.app_context():
20+
app_initializations()
21+
yield URL(f"http://{hostname}:{port}")
22+
loopback.deactivate_address((hostname, port))
5223

53-
@pytest.fixture
54-
def mailboxer(mailboxer_url):
24+
25+
@pytest.fixture(name="mailboxer")
26+
def mailboxer_fx(mailboxer_url):
5527
return Mailboxer(mailboxer_url)
5628

57-
@pytest.fixture(
58-
params=[10]
59-
)
60-
def num_objects(request):
29+
30+
@pytest.fixture(params=[10], name="num_objects")
31+
def num_objects_fx(request):
6132
return request.param
6233

34+
6335
@pytest.fixture
6436
def mailbox(request, mailboxer):
6537
returned = mailboxer.create_mailbox("mailbox@mailboxer.com")
6638
request.addfinalizer(returned.delete)
6739
return returned
6840

41+
6942
@pytest.fixture
7043
def mailboxes(mailboxer, num_objects):
7144
return [
7245
mailboxer.create_mailbox("mailbox{0}@mailboxer.com".format(i))
73-
for i in range(num_objects)]
46+
for i in range(num_objects)
47+
]

0 commit comments

Comments
 (0)