mirror of
https://github.com/ivuorinen/aeonview.git
synced 2026-02-21 21:50:19 +00:00
chore(lint): more linting fixes
This commit is contained in:
@@ -20,7 +20,6 @@ indent_style = tab
|
|||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
max_line_length = 100
|
|
||||||
|
|
||||||
[*.yml]
|
[*.{yml,toml,md}]
|
||||||
max_line_length = 100
|
max_line_length = 200
|
||||||
|
|||||||
44
.github/CONTRIBUTING.md
vendored
44
.github/CONTRIBUTING.md
vendored
@@ -6,22 +6,22 @@ This guide will help you get started.
|
|||||||
## 🛠 Project Setup
|
## 🛠 Project Setup
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-username/aeonview.git
|
git clone https://github.com/your-username/aeonview.git
|
||||||
cd aeonview
|
cd aeonview
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Set up your environment:
|
2. Set up your environment:
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install -r dev-requirements.txt
|
pip install -r dev-requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Install pre-commit hooks:
|
3. Install pre-commit hooks:
|
||||||
```bash
|
```bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
## ✅ Development Workflow
|
## ✅ Development Workflow
|
||||||
|
|
||||||
@@ -46,20 +46,20 @@ make lint # check for lint errors
|
|||||||
## ✅ Submitting a Pull Request
|
## ✅ Submitting a Pull Request
|
||||||
|
|
||||||
1. Create a feature branch:
|
1. Create a feature branch:
|
||||||
```bash
|
```bash
|
||||||
git checkout -b feature/my-new-feature
|
git checkout -b feature/my-new-feature
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Commit your changes:
|
2. Commit your changes:
|
||||||
```bash
|
```bash
|
||||||
git add .
|
git add .
|
||||||
git commit -m "feat: add support for X"
|
git commit -m "feat: add support for X"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Push and open a pull request:
|
3. Push and open a pull request:
|
||||||
```bash
|
```bash
|
||||||
git push origin feature/my-new-feature
|
git push origin feature/my-new-feature
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Follow the PR template and link any relevant issues.
|
4. Follow the PR template and link any relevant issues.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
```markdown
|
```markdown
|
||||||
_)
|
# _)
|
||||||
_` | -_) _ \ \ \ \ / | -_) \ \ \ /
|
# _` | -_) _ \ \ \ \ / | -_) \ \ \ /
|
||||||
\__,_| \___| \___/ _| _| \_/ _| \___| \_/\_/
|
# \__,_| \___| \___/ _| _| \_/ _| \___| \_/\_/
|
||||||
aeonview - a simple timelapse tool
|
# aeonview - a simple timelapse tool
|
||||||
```
|
```
|
||||||
|
|
||||||
**aeonview** is a Python-based tool for generating timelapse videos
|
**aeonview** is a Python-based tool for generating timelapse videos
|
||||||
|
|||||||
@@ -365,12 +365,12 @@ class AeonViewHelpers:
|
|||||||
|
|
||||||
if url.endswith(".png"):
|
if url.endswith(".png"):
|
||||||
return ".png"
|
return ".png"
|
||||||
elif url.endswith(".gif"):
|
if url.endswith(".gif"):
|
||||||
return ".gif"
|
return ".gif"
|
||||||
elif url.endswith(".webp"):
|
if url.endswith(".webp"):
|
||||||
return ".webp"
|
return ".webp"
|
||||||
else:
|
|
||||||
return ".jpg"
|
return ".jpg"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup_logger(verbose: bool):
|
def setup_logger(verbose: bool):
|
||||||
|
|||||||
@@ -92,27 +92,35 @@ def test_get_image_paths_valid():
|
|||||||
date = datetime(2025, 4, 10, 12, 30, 45)
|
date = datetime(2025, 4, 10, 12, 30, 45)
|
||||||
aeon_view_images = AeonViewImages(destination_base, url)
|
aeon_view_images = AeonViewImages(destination_base, url)
|
||||||
paths = aeon_view_images.get_image_paths(url, destination_base, date)
|
paths = aeon_view_images.get_image_paths(url, destination_base, date)
|
||||||
|
|
||||||
|
a_p = destination_base / "2025-04" / "10" / "12-30-45.jpg"
|
||||||
|
|
||||||
assert paths["url"] == url
|
assert paths["url"] == url
|
||||||
assert paths["file"] == "12-30-45.jpg"
|
assert paths["file"] == "12-30-45.jpg"
|
||||||
assert paths["destinations"][
|
assert paths["destinations"]["file"] == a_p
|
||||||
"file"] == destination_base / "2025-04" / "10" / "12-30-45.jpg"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_image_paths_invalid_url():
|
def test_get_image_paths_invalid_url():
|
||||||
with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log:
|
with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log:
|
||||||
aeon_view_images = AeonViewImages(default_test_path, "invalid-url")
|
aeon_view_images = AeonViewImages(default_test_path, "invalid-url")
|
||||||
aeon_view_images.get_image_paths("invalid-url", default_test_path,
|
aeon_view_images.get_image_paths(
|
||||||
datetime(2025, 4, 10))
|
"invalid-url",
|
||||||
|
default_test_path,
|
||||||
|
datetime(2025, 4, 10)
|
||||||
|
)
|
||||||
assert AeonViewMessages.INVALID_URL in log.call_args[0][0]
|
assert AeonViewMessages.INVALID_URL in log.call_args[0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_get_image_paths_invalid_date():
|
def test_get_image_paths_invalid_date():
|
||||||
with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log:
|
with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log:
|
||||||
aeon_view_images = AeonViewImages(default_test_path,
|
aeon_view_images = AeonViewImages(
|
||||||
f"{default_image_domain}.jpg")
|
default_test_path,
|
||||||
aeon_view_images.get_image_paths(f"{default_image_domain}.jpg",
|
f"{default_image_domain}.jpg"
|
||||||
default_test_path,
|
)
|
||||||
"invalid-date") # pyright: ignore [reportArgumentType]
|
aeon_view_images.get_image_paths(
|
||||||
|
f"{default_image_domain}.jpg",
|
||||||
|
default_test_path,
|
||||||
|
"invalid-date" # pyright: ignore [reportArgumentType]
|
||||||
|
)
|
||||||
assert AeonViewMessages.INVALID_DATE in log.call_args[0][0]
|
assert AeonViewMessages.INVALID_DATE in log.call_args[0][0]
|
||||||
|
|
||||||
|
|
||||||
@@ -138,8 +146,11 @@ def test_download_image_success(mock_get):
|
|||||||
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
||||||
destination = Path(temp_file.name)
|
destination = Path(temp_file.name)
|
||||||
avi.download_image(destination)
|
avi.download_image(destination)
|
||||||
mock_get.assert_called_once_with(f"{default_image_domain}.jpg",
|
mock_get.assert_called_once_with(
|
||||||
stream=True, timeout=10)
|
f"{default_image_domain}.jpg",
|
||||||
|
stream=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
assert destination.exists()
|
assert destination.exists()
|
||||||
|
|
||||||
|
|
||||||
@@ -161,8 +172,13 @@ def test_download_image_failure(mock_get):
|
|||||||
@mock.patch("aeonview.AeonViewHelpers.mkdir_p")
|
@mock.patch("aeonview.AeonViewHelpers.mkdir_p")
|
||||||
@mock.patch("subprocess.run")
|
@mock.patch("subprocess.run")
|
||||||
def test_generate_daily_video(mock_subprocess_run, mock_mkdir_p):
|
def test_generate_daily_video(mock_subprocess_run, mock_mkdir_p):
|
||||||
args = argparse.Namespace(simulate=False, fps=10, day="01", month="04",
|
args = argparse.Namespace(
|
||||||
year="2025")
|
simulate=False,
|
||||||
|
fps=10,
|
||||||
|
day="01",
|
||||||
|
month="04",
|
||||||
|
year="2025"
|
||||||
|
)
|
||||||
avv = AeonViewVideos(default_test_path, args)
|
avv = AeonViewVideos(default_test_path, args)
|
||||||
with mock.patch("aeonview.logging.info") as log:
|
with mock.patch("aeonview.logging.info") as log:
|
||||||
avv.generate_daily_video()
|
avv.generate_daily_video()
|
||||||
@@ -177,31 +193,46 @@ def test_generate_daily_video(mock_subprocess_run, mock_mkdir_p):
|
|||||||
|
|
||||||
@mock.patch("aeonview.AeonViewHelpers.mkdir_p")
|
@mock.patch("aeonview.AeonViewHelpers.mkdir_p")
|
||||||
def test_generate_daily_video_simulate(mock_mkdir_p):
|
def test_generate_daily_video_simulate(mock_mkdir_p):
|
||||||
args = argparse.Namespace(simulate=True, fps=10, day="01", month="04",
|
args = argparse.Namespace(
|
||||||
year="2025")
|
simulate=True,
|
||||||
|
fps=10,
|
||||||
|
day="01",
|
||||||
|
month="04",
|
||||||
|
year="2025"
|
||||||
|
)
|
||||||
avv = AeonViewVideos(default_test_path, args)
|
avv = AeonViewVideos(default_test_path, args)
|
||||||
avv.generate_daily_video()
|
avv.generate_daily_video()
|
||||||
mock_mkdir_p.assert_not_called()
|
mock_mkdir_p.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_generate_monthly_video_not_implemented():
|
def test_generate_monthly_video_not_implemented():
|
||||||
args = argparse.Namespace(simulate=False, fps=10, day="01", month="04",
|
args = argparse.Namespace(
|
||||||
year="2025")
|
simulate=False,
|
||||||
|
fps=10,
|
||||||
|
day="01",
|
||||||
|
month="04",
|
||||||
|
year="2025"
|
||||||
|
)
|
||||||
avv = AeonViewVideos(default_test_path, args)
|
avv = AeonViewVideos(default_test_path, args)
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
avv.generate_monthly_video(Path("/tmp"))
|
avv.generate_monthly_video(Path("/tmp"))
|
||||||
|
|
||||||
|
|
||||||
def test_generate_yearly_video_not_implemented():
|
def test_generate_yearly_video_not_implemented():
|
||||||
args = argparse.Namespace(simulate=False, fps=10, day="01", month="04",
|
args = argparse.Namespace(
|
||||||
year="2025")
|
simulate=False,
|
||||||
|
fps=10,
|
||||||
|
day="01",
|
||||||
|
month="04",
|
||||||
|
year="2025"
|
||||||
|
)
|
||||||
avv = AeonViewVideos(default_test_path, args)
|
avv = AeonViewVideos(default_test_path, args)
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
avv.generate_yearly_video(Path("/tmp"))
|
avv.generate_yearly_video(Path("/tmp"))
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--url",
|
@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--url",
|
||||||
f"{default_image_domain}.jpg"])
|
f"{default_image_domain}.jpg"])
|
||||||
def test_parse_arguments_image_mode():
|
def test_parse_arguments_image_mode():
|
||||||
args, _ = AeonViewHelpers.parse_arguments()
|
args, _ = AeonViewHelpers.parse_arguments()
|
||||||
assert args.mode == "image"
|
assert args.mode == "image"
|
||||||
@@ -210,7 +241,7 @@ def test_parse_arguments_image_mode():
|
|||||||
|
|
||||||
|
|
||||||
@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--project",
|
@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--project",
|
||||||
f"{default_project}"])
|
f"{default_project}"])
|
||||||
def test_parse_arguments_video_mode():
|
def test_parse_arguments_video_mode():
|
||||||
args, _ = AeonViewHelpers.parse_arguments()
|
args, _ = AeonViewHelpers.parse_arguments()
|
||||||
assert args.mode == "video"
|
assert args.mode == "video"
|
||||||
@@ -234,8 +265,10 @@ def test_parse_arguments_fps():
|
|||||||
assert args.fps == 30
|
assert args.fps == 30
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("sys.argv",
|
@mock.patch(
|
||||||
["aeonview.py", "--mode", "video", "--generate", "2023-10-01"])
|
"sys.argv",
|
||||||
|
["aeonview.py", "--mode", "video", "--generate", "2023-10-01"]
|
||||||
|
)
|
||||||
def test_parse_arguments_generate_date():
|
def test_parse_arguments_generate_date():
|
||||||
args, _ = AeonViewHelpers.parse_arguments()
|
args, _ = AeonViewHelpers.parse_arguments()
|
||||||
assert args.mode == "video"
|
assert args.mode == "video"
|
||||||
@@ -268,7 +301,10 @@ def test_image_simulation(mock_download_image, mock_mkdir_p):
|
|||||||
args.simulate = True
|
args.simulate = True
|
||||||
args.date = "2025-04-10 12:30:45"
|
args.date = "2025-04-10 12:30:45"
|
||||||
avi = AeonViewImages(
|
avi = AeonViewImages(
|
||||||
default_test_path, f"{default_image_domain}.jpg", args)
|
default_test_path,
|
||||||
|
f"{default_image_domain}.jpg",
|
||||||
|
args
|
||||||
|
)
|
||||||
with mock.patch("aeonview.logging.info") as log:
|
with mock.patch("aeonview.logging.info") as log:
|
||||||
avi.get_current_image()
|
avi.get_current_image()
|
||||||
mock_mkdir_p.assert_not_called()
|
mock_mkdir_p.assert_not_called()
|
||||||
@@ -302,7 +338,8 @@ def test_setup_logger_verbose(mock_basic_config):
|
|||||||
AeonViewHelpers.setup_logger(verbose=True)
|
AeonViewHelpers.setup_logger(verbose=True)
|
||||||
mock_basic_config.assert_called_once_with(
|
mock_basic_config.assert_called_once_with(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
format="[%(levelname)s] %(message)s")
|
format="[%(levelname)s] %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("logging.basicConfig")
|
@mock.patch("logging.basicConfig")
|
||||||
@@ -310,4 +347,5 @@ def test_setup_logger_non_verbose(mock_basic_config):
|
|||||||
AeonViewHelpers.setup_logger(verbose=False)
|
AeonViewHelpers.setup_logger(verbose=False)
|
||||||
mock_basic_config.assert_called_once_with(
|
mock_basic_config.assert_called_once_with(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format="[%(levelname)s] %(message)s")
|
format="[%(levelname)s] %(message)s"
|
||||||
|
)
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ python_files = ["*_test.py"]
|
|||||||
ignore-patterns='^\.#'
|
ignore-patterns='^\.#'
|
||||||
ignore-paths='^\.#'
|
ignore-paths='^\.#'
|
||||||
ignore='CVS,.venv'
|
ignore='CVS,.venv'
|
||||||
disable='attribute-defined-outside-init,invalid-name,missing-docstring,protected-access,too-few-public-methods,format'
|
disable='attribute-defined-outside-init,invalid-name,missing-docstring,protected-access,too-many-instance-attributes,too-few-public-methods,format'
|
||||||
|
|||||||
Reference in New Issue
Block a user