├── requirements.txt
├── tests
├── __init__.py
└── test_magzdb.py
├── magzdb
├── version.py
├── __init__.py
├── downloader.py
├── cli.py
└── magzdb.py
├── requirements_dev.txt
├── Dockerfile
├── MANIFEST.in
├── setup.cfg
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── continuous-integration-pip.yml
│ └── continuous-integration-publish.yml
├── .coveragerc
├── .pre-commit-config.yaml
├── LICENSE
├── setup.py
├── .gitignore
├── Makefile
├── CONTRIBUTING.md
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 | loguru>=0.5.3
2 | requests>=2.24.0
3 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Unit test package for magzdb."""
2 |
--------------------------------------------------------------------------------
/magzdb/version.py:
--------------------------------------------------------------------------------
1 | __author__ = """Aakash Gajjar"""
2 | __email__ = "skyqutip@gmail.com"
3 | __version__ = "1.2.0"
4 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | black==23.11.0
2 | blacken-docs==1.16.0
3 | coverage==7.3.2
4 | pre-commit==3.5.0
5 | pydocstyle==6.3.0
6 | pytest==7.4.3
7 | twine==4.0.2
8 | watchdog==3.0.0
9 | wheel==0.42.0
10 |
--------------------------------------------------------------------------------
/magzdb/__init__.py:
--------------------------------------------------------------------------------
1 | """Top-level package for magzdb."""
2 | # For relative imports to work in Python 3.6
3 | import os
4 | import sys
5 |
6 | sys.path.append(os.path.dirname(os.path.realpath(__file__)))
7 |
8 | from magzdb.magzdb import Magzdb
9 |
10 | __all__ = ["Magzdb"]
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10-slim-bullseye
2 |
3 | RUN pip install -U magzdb
4 |
5 | RUN apt update && \
6 | apt install wget --yes && \
7 | apt-get clean autoclean && \
8 | apt-get autoremove --yes
9 |
10 | WORKDIR /tmp
11 |
12 | ENTRYPOINT [ "magzdb", "--downloader", "wget" ]
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CONTRIBUTING.md
2 | include LICENSE
3 | include README.md
4 | include requirements.txt
5 |
6 | recursive-include tests *
7 | recursive-exclude * __pycache__
8 | recursive-exclude * *.py[co]
9 |
10 | recursive-include docs *.md conf.py Makefile make.bat *.jpg *.png *.gif
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.1.0
3 | commit = True
4 | tag = True
5 |
6 | [bumpversion:file:magzdb/version.py]
7 | search = __version__ = "{current_version}"
8 | replace = __version__ = "{new_version}"
9 |
10 | [bdist_wheel]
11 | universal = 1
12 |
13 | [flake8]
14 | exclude = docs
15 |
16 | [aliases]
17 | # Define setup.py command aliases here
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 | end_of_line = lf
12 |
13 | [*.bat]
14 | indent_style = tab
15 | end_of_line = crlf
16 |
17 | [LICENSE]
18 | insert_final_newline = false
19 |
20 | [Makefile]
21 | indent_style = tab
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * magzdb version:
2 | * Python version:
3 | * Operating System:
4 |
5 | ### Description
6 |
7 | Describe what you were trying to get done.
8 | Tell us what happened, what went wrong, and what you expected to happen.
9 |
10 | ### What I Did
11 |
12 | ```
13 | Paste the command(s) you ran and the output.
14 | If there was a crash, please include the traceback here.
15 | ```
16 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [report]
2 | exclude_lines =
3 | pragma: no cover
4 | except re.error as e:
5 | except requests.ConnectionError as e:
6 | except requests.HTTPError as e:
7 | except FileExistsError:
8 | except requests.exceptions.RequestException:
9 | except FileNotFoundError:
10 | except AttributeError:
11 | os.remove(dest)
12 | continue
13 | return
14 |
15 | [run]
16 | omit =
17 | .eggs/*
18 | venv/*
19 | setup.py
20 | magzdb/cli.py
21 | magzdb/version.py
22 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 | repos:
4 | - repo: https://github.com/pre-commit/pre-commit-hooks
5 | rev: v2.4.0
6 | hooks:
7 | - id: check-added-large-files
8 | - id: check-yaml
9 | - id: detect-private-key
10 | - id: end-of-file-fixer
11 | - id: requirements-txt-fixer
12 | - id: trailing-whitespace
13 | - repo: https://github.com/asottile/reorder_python_imports
14 | rev: v2.3.0
15 | hooks:
16 | - id: reorder-python-imports
17 | - repo: https://github.com/psf/black
18 | rev: 19.10b0
19 | hooks:
20 | - id: black
21 | - repo: https://github.com/asottile/blacken-docs
22 | rev: v1.7.0
23 | hooks:
24 | - id: blacken-docs
25 | additional_dependencies: [black==19.10b0]
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022, Aakash Gajjar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/continuous-integration-pip.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | python-version: ["3.10"]
12 |
13 | steps:
14 | - uses: actions/cache@v2
15 | with:
16 | path: ~/.cache/pip
17 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
18 | restore-keys: |
19 | ${{ runner.os }}-pip-
20 | - uses: actions/checkout@v2
21 | - name: Set up Python ${{ matrix.python-version }}
22 | uses: actions/setup-python@v2
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install downloaders
26 | run: |
27 | sudo apt-get install wget curl
28 | sudo snap install aria2c
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install pytest
33 | pip install pytest-cov
34 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
35 | - name: Test with pytest
36 | run: |
37 | pytest --cov=./ --cov-report=xml
38 | - name: Upload coverage to Codecov
39 | uses: codecov/codecov-action@v1
40 |
--------------------------------------------------------------------------------
/.github/workflows/continuous-integration-publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*'
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | python-version: [3.8]
15 |
16 | steps:
17 | - uses: actions/cache@v2
18 | with:
19 | path: ~/.cache/pip
20 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
21 | restore-keys: |
22 | ${{ runner.os }}-pip-
23 | - uses: actions/checkout@v2
24 | - name: Set up Python ${{ matrix.python-version }}
25 | uses: actions/setup-python@v2
26 | with:
27 | python-version: ${{ matrix.python-version }}
28 | - name: Install dependencies
29 | run: |
30 | python -m pip install --upgrade pip
31 | pip install setuptools
32 | pip install wheel
33 | pip install twine
34 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
35 | - name: Build and publish
36 | env:
37 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
38 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
39 | run: |
40 | python setup.py sdist bdist_wheel
41 | twine upload dist/*
42 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """The setup script."""
3 | from setuptools import find_packages
4 | from setuptools import setup
5 |
6 | from magzdb.version import __version__
7 |
8 | with open("README.md") as readme_file:
9 | readme = readme_file.read()
10 |
11 | with open("requirements.txt", "r") as file:
12 | requirements = [r for r in file.readlines() if len(r) > 0]
13 |
14 | setup_requirements = []
15 |
16 | test_requirements = ["pytest"].extend(requirements)
17 |
18 | setup(
19 | author="Aakash Gajjar",
20 | author_email="skyqutip@gmail.com",
21 | python_requires=">=3.5",
22 | classifiers=[
23 | "Development Status :: 5 - Production/Stable",
24 | "Intended Audience :: End Users/Desktop ",
25 | "License :: OSI Approved :: MIT License",
26 | "Natural Language :: English",
27 | "Programming Language :: Python :: 3",
28 | "Programming Language :: Python :: 3.6",
29 | "Programming Language :: Python :: 3.7",
30 | "Programming Language :: Python :: 3.8",
31 | "Programming Language :: Python :: 3.9",
32 | "Programming Language :: Python :: 3.10",
33 | ],
34 | description="Magzdb.org Downloader",
35 | entry_points={"console_scripts": ["magzdb=magzdb.cli:main",],},
36 | include_package_data=True,
37 | install_requires=requirements,
38 | keywords="magzdb",
39 | license="MIT license",
40 | long_description=readme,
41 | long_description_content_type="text/markdown",
42 | name="magzdb",
43 | packages=find_packages(include=["magzdb", "magzdb.*"]),
44 | setup_requires=setup_requirements,
45 | test_suite="tests",
46 | tests_require=test_requirements,
47 | url="https://github.com/skyme5/magzdb",
48 | version=__version__,
49 | zip_safe=False,
50 | )
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # mypy
102 | .mypy_cache/
103 |
104 | # IDE settings
105 | .vscode/
106 |
107 | *.pdf
108 |
109 | .test-data/
110 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean clean-test clean-pyc clean-build docs help
2 | .DEFAULT_GOAL := help
3 |
4 | define BROWSER_PYSCRIPT
5 | import os, webbrowser, sys
6 |
7 | from urllib.request import pathname2url
8 |
9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
10 | endef
11 | export BROWSER_PYSCRIPT
12 |
13 | define PRINT_HELP_PYSCRIPT
14 | import re, sys
15 |
16 | for line in sys.stdin:
17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
18 | if match:
19 | target, help = match.groups()
20 | print("%-20s %s" % (target, help))
21 | endef
22 | export PRINT_HELP_PYSCRIPT
23 |
24 | BROWSER := python -c "$$BROWSER_PYSCRIPT"
25 |
26 | help:
27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
28 |
29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
30 |
31 | clean-build: ## remove build artifacts
32 | rm -fr build/
33 | rm -fr dist/
34 | rm -fr .eggs/
35 | find . -name '*.egg-info' -exec rm -fr {} +
36 | find . -name '*.egg' -exec rm -rf {} +
37 |
38 | clean-pyc: ## remove Python file artifacts
39 | find . -name '*.pyc' -exec rm -f {} +
40 | find . -name '*.pyo' -exec rm -f {} +
41 | find . -name '*~' -exec rm -f {} +
42 | find . -name '__pycache__' -exec rm -fr {} +
43 |
44 | clean-test: ## remove test and coverage artifacts
45 | rm -fr .tox/
46 | rm -f .coverage
47 | rm -fr htmlcov/
48 | rm -fr .pytest_cache
49 |
50 | lint: ## check style with flake8
51 | black magzdb tests
52 |
53 | test: ## run tests quickly with the default Python
54 | pytest
55 |
56 | coverage: ## check code coverage quickly with the default Python
57 | coverage run --source magzdb setup.py test
58 | coverage report -m
59 | coverage html
60 | $(BROWSER) htmlcov/index.html
61 |
62 | release: dist ## package and upload a release
63 | twine upload dist/*
64 |
65 | dist: clean ## builds source and wheel package
66 | python setup.py sdist
67 | python setup.py bdist_wheel
68 | ls -l dist
69 |
70 | install: clean ## install the package to the active Python's site-packages
71 | python setup.py install
72 |
73 | deps:
74 | pip install -r requirements.txt -r requirements_dev.txt
75 |
--------------------------------------------------------------------------------
/magzdb/downloader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 |
4 | import requests
5 | from loguru import logger
6 |
7 | DOWNLOADER_LIST = ["aria2", "curl", "self", "wget"]
8 |
9 |
10 | def download_file(url: str, dest: str):
11 | USER_AGENT = (
12 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
13 | "AppleWebKit/537.36 (KHTML, like Gecko)"
14 | "Chrome/84.0.4147.68 Safari/537.36"
15 | )
16 |
17 | if len(os.path.dirname(dest)) > 0:
18 | os.makedirs(os.path.dirname(dest), exist_ok=True)
19 |
20 | try:
21 | if os.path.isfile(dest) and os.path.getsize(dest) == 0: # pragma: no cover
22 | os.remove(dest)
23 | except FileNotFoundError:
24 | pass
25 |
26 | try:
27 | with open(dest, "xb") as handle:
28 | headers = {"User-Agent": USER_AGENT}
29 | response = requests.get(url, stream=True, timeout=160, headers=headers)
30 | if response.status_code != requests.Response.ok:
31 | response.raise_for_status()
32 |
33 | for data in response.iter_content(chunk_size=8192):
34 | handle.write(data)
35 | handle.close()
36 | except FileExistsError:
37 | pass
38 | except requests.exceptions.RequestException:
39 | logger.error(f"File {dest} not found on Server {url}".format(dest))
40 | pass
41 |
42 | if os.path.getsize(dest) == 0: # pragma: no cover
43 | os.remove(dest)
44 |
45 |
46 | def external_downloader(dir: str, filename: str, url: str, name: str, debug: bool):
47 | parameters = { # pragma: no cover
48 | "aria2": [
49 | "aria2c",
50 | "--retry-wait=3",
51 | "-c",
52 | f"--dir={dir}",
53 | f"--out={filename}",
54 | url,
55 | ],
56 | "wget": ["wget", "-c", "-O", os.path.join(dir, filename), url],
57 | "curl": ["curl", "-C", "-", url, "--output", os.path.join(dir, filename)],
58 | }
59 |
60 | silent_flags = { # pragma: no cover
61 | "aria2": ["-q"],
62 | "wget": ["-nv"],
63 | "curl": ["--silent"],
64 | }
65 |
66 | if debug:
67 | print(parameters.get(name))
68 |
69 | return parameters.get(name) if debug else parameters.get(name) + silent_flags[name]
70 |
--------------------------------------------------------------------------------
/magzdb/cli.py:
--------------------------------------------------------------------------------
1 | """Console script for magzdb."""
2 | import argparse
3 | import signal
4 | import sys
5 |
6 | from loguru import logger
7 |
8 | from magzdb.magzdb import Magzdb
9 | from magzdb.version import __version__
10 |
11 |
12 | def handler(signum, frame):
13 | exit(0)
14 |
15 |
16 | def main():
17 | """Console script for magzdb."""
18 | parser = argparse.ArgumentParser(description="Magzdb.org Downloader")
19 |
20 | parser.add_argument(
21 | "-V",
22 | "--version",
23 | action="version",
24 | help="Print program version and exit",
25 | version=__version__,
26 | )
27 |
28 | parser.add_argument(
29 | "-i",
30 | "--id",
31 | help="ID of the Magazine to Download. eg. http://magzdb.org/j/
2 |