Fixes #17 #18 Use pdm + pyinstaller

This commit is contained in:
sebthom
2022-01-30 07:31:13 +01:00
parent 8e445f08c6
commit 1e1cffeab7
15 changed files with 1354 additions and 254 deletions

View File

@@ -1,3 +1,6 @@
# Copyright (C) 2022 Sebastian Thomschke and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions
name: Build name: Build
@@ -41,28 +44,30 @@ jobs:
python --version python --version
pip install .[dev] python -m pip install --upgrade pip
- name: python setup.py check pip install pdm
run: | pip install pdm-packer
# https://docs.python.org/3/distutils/apiref.html#module-distutils.command.check
python setup.py check --metadata --strict --verbose
- name: bandit # https://github.com/python/mypy/issues/11829
pip install -t __pypackages__/3.10/lib git+git://github.com/python/mypy.git@9b3147701f054bf8ef42bd96e33153b05976a5e1
# https://github.com/pdm-project/pdm/issues/728#issuecomment-1021771200
pip install -t __pypackages__/3.10/lib selenium
pdm install
- name: Scanning for security issues using bandit
run: | run: |
set -eux pdm run bandit
bandit -c pyproject.toml --exclude '*/.eggs/*' -r .
- name: pylint - name: pylint
run: | run: |
set -eux pdm run pylint
pip install pylint
pylint kleinanzeigen_bot
- name: pytest - name: pytest
run: | run: |
set -eux pdm run pytest
python -m pytest
- name: run kleinanzeigen_bot - name: run kleinanzeigen_bot
run: | run: |
@@ -74,9 +79,9 @@ jobs:
set -eux set -eux
python -m kleinanzeigen_bot help pdm run app help
python -m kleinanzeigen_bot version pdm run app version
python -m kleinanzeigen_bot verify pdm run app verify
- name: "Build docker image" - name: "Build docker image"
if: startsWith(matrix.os, 'linux') if: startsWith(matrix.os, 'linux')
@@ -87,10 +92,44 @@ jobs:
docker run --rm kleinanzeigen-bot/kleinanzeigen-bot help docker run --rm kleinanzeigen-bot/kleinanzeigen-bot help
- name: py2exe - name: "Install: binutils (strip)"
if: startsWith(matrix.os, 'windows') if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get --no-install-recommends install -y binutils
- name: "Install: UPX"
run: | run: |
python setup.py py2exe set -eu
case "${{ matrix.target }}" in
macos-*)
brew install upx
;;
ubuntu-*)
mkdir /opt/upx
upx_download_url=$(curl -fsSL https://api.github.com/repos/upx/upx/releases/latest | grep browser_download_url | grep amd64_linux.tar.xz | cut "-d\"" -f4)
echo "Downloading [$upx_download_url]..."
curl -fL $upx_download_url | tar Jxv -C /opt/upx --strip-components=1
echo "/opt/upx" >> $GITHUB_PATH
;;
windows-*)
upx_download_url=$(curl -fsSL https://api.github.com/repos/upx/upx/releases/latest | grep browser_download_url | grep win64.zip | cut "-d\"" -f4)
echo "Downloading [$upx_download_url]..."
curl -fL -o /tmp/upx.zip $upx_download_url
echo "Extracting upx.zip..."
mkdir /tmp/upx
7z e /tmp/upx.zip -o/tmp/upx *.exe -r
echo "$(cygpath -wa /tmp/upx)" >> $GITHUB_PATH
;;
esac
- name: pyinstaller
run: |
set -eux
pdm run pyinstaller
ls -l dist ls -l dist
- name: run kleinanzeigen_bot.exe - name: run kleinanzeigen_bot.exe

2
.gitignore vendored
View File

@@ -11,11 +11,13 @@ _LOCAL/
kleinanzeigen_bot/version.py kleinanzeigen_bot/version.py
# python # python
/__pypackages__
__pycache__ __pycache__
/build /build
/dist /dist
/.eggs /.eggs
/*.egg-info /*.egg-info
/.pdm.toml
# Eclipse # Eclipse
/.project /.project

View File

@@ -68,12 +68,17 @@ It is a spiritual successor to [AnzeigenOrg/ebayKleinanzeigen](https://github.co
cd kleinanzeigen-bot cd kleinanzeigen-bot
``` ```
1. Install the Python dependencies using: 1. Install the Python dependencies using:
``` ```bash
pip install . pip install pdm
# temporary workaround for https://github.com/pdm-project/pdm/issues/728#issuecomment-1021771200
pip install -t __pypackages__/3.10/lib selenium
pdm install
``` ```
1. Run the app: 1. Run the app:
``` ```
python -m kleinanzeigen_bot --help pdm run app --help
``` ```
### Installation using Docker ### Installation using Docker
@@ -243,14 +248,14 @@ updated_on: # set automatically
> Please read [CONTRIBUTING.md](CONTRIBUTING.md) before contributing code. Thank you! > Please read [CONTRIBUTING.md](CONTRIBUTING.md) before contributing code. Thank you!
- Installing dev dependencies: `pip install .[dev]` - Running unit tests: `pdm run pytest`
- Running unit tests: `python -m pytest` or `pytest` - Running linter: `pdm run pylint`
- Running linter: `python -m pylint kleinanzeigen_bot` or `pylint kleinanzeigen_bot`
- Displaying effective version:`python setup.py --version` - Displaying effective version:`python setup.py --version`
- Creating Windows executable: `python setup.py py2exe` - Creating Windows executable: `pdm run pyinstaller`
- Application bootstrap works like this: - Application bootstrap works like this:
```python ```python
python -m kleinanzeigen_bot pdm run app
|-> executes 'python -m kleinanzeigen_bot'
|-> executes 'kleinanzeigen_bot/__main__.py' |-> executes 'kleinanzeigen_bot/__main__.py'
|-> executes main() function of 'kleinanzeigen_bot/__init__.py' |-> executes main() function of 'kleinanzeigen_bot/__init__.py'
|-> executes KleinanzeigenBot().run() |-> executes KleinanzeigenBot().run()

View File

@@ -6,7 +6,7 @@
###################### ######################
# runtime image base # runtime image base
###################### ######################
FROM python:3-slim as runtime-base-image FROM debian:stable-slim as runtime-base-image
LABEL maintainer="Sebastian Thomschke" LABEL maintainer="Sebastian Thomschke"
@@ -36,25 +36,47 @@ RUN set -eu \
FROM python:3-slim AS build-image FROM python:3-slim AS build-image
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends -y git \ # install required libraries
&& python -m pip install --upgrade pip && apt-get install --no-install-recommends -y \
binutils `# required by pyinstaller` \
git `# required by pdm to generate app version` \
curl xz-utils `# required to install upx` \
# install upx
# upx is currently not supported on Linux, see https://github.com/pyinstaller/pyinstaller/discussions/6275
#&& mkdir /opt/upx \
#&& upx_download_url=$(curl -fsSL https://api.github.com/repos/upx/upx/releases/latest | grep browser_download_url | grep amd64_linux.tar.xz | cut "-d\"" -f4) \
#&& echo "Downloading [$upx_download_url]..." \
#&& curl -fL $upx_download_url | tar Jxv -C /opt/upx --strip-components=1 \
# upgrade pip
&& python -m pip install --upgrade pip \
# install pdm
&& pip install pdm
ENV PATH="/opt/upx:${PATH}"
COPY kleinanzeigen_bot /opt/app/kleinanzeigen_bot COPY kleinanzeigen_bot /opt/app/kleinanzeigen_bot
COPY .git /opt/app/.git COPY .git /opt/app/.git
COPY *.py *.txt *.toml /opt/app/ COPY README.md pdm.lock pyinstaller.spec pyproject.toml /opt/app/
RUN cd /opt/app \ RUN cd /opt/app \
&& ls -la . \ && ls -la . \
&& pip install --user . \ # https://github.com/python/mypy/issues/11829
# generates version.py && pip install -t __pypackages__/3.10/lib git+git://github.com/python/mypy.git@9b3147701f054bf8ef42bd96e33153b05976a5e1 \
&& python setup.py --version # https://github.com/pdm-project/pdm/issues/728#issuecomment-1021771200
&& pip install -t __pypackages__/3.10/lib selenium \
&& pdm install \
&& ls -la kleinanzeigen_bot \
&& pdm run pyinstaller \
&& ls -l dist
RUN /opt/app/dist/kleinanzeigen-bot --help
###################### ######################
# final image # final image
###################### ######################
FROM runtime-base-image FROM runtime-base-image
COPY --from=build-image /root/.local /root/.local COPY --from=build-image /opt/app/dist/kleinanzeigen-bot /opt/kleinanzeigen-bot
ARG BUILD_DATE ARG BUILD_DATE
ARG GIT_COMMIT_HASH ARG GIT_COMMIT_HASH

View File

@@ -21,4 +21,4 @@ fi
cd /mnt/data cd /mnt/data
python -m kleinanzeigen_bot --config $CONFIG_FILE "$@" /opt/kleinanzeigen-bot --config $CONFIG_FILE "$@"

View File

@@ -4,6 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
""" """
import atexit, copy, getopt, glob, json, logging, os, signal, sys, textwrap, time, urllib import atexit, copy, getopt, glob, json, logging, os, signal, sys, textwrap, time, urllib
from datetime import datetime from datetime import datetime
import importlib.metadata
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from typing import Any, Dict, Final, Iterable from typing import Any, Dict, Final, Iterable
@@ -20,11 +21,6 @@ LOG_ROOT:Final[logging.Logger] = logging.getLogger()
LOG:Final[logging.Logger] = logging.getLogger("kleinanzeigen_bot") LOG:Final[logging.Logger] = logging.getLogger("kleinanzeigen_bot")
LOG.setLevel(logging.INFO) LOG.setLevel(logging.INFO)
try:
from .version import version as VERSION
except ModuleNotFoundError:
VERSION = "unknown"
class KleinanzeigenBot(SeleniumMixin): class KleinanzeigenBot(SeleniumMixin):
@@ -50,7 +46,9 @@ class KleinanzeigenBot(SeleniumMixin):
def __del__(self): def __del__(self):
if self.file_log: if self.file_log:
LOG_ROOT.removeHandler(self.file_log) LOG_ROOT.removeHandler(self.file_log)
super().__del__()
def get_version(self) -> str:
return importlib.metadata.version(__package__)
def run(self, args:Iterable[str]) -> None: def run(self, args:Iterable[str]) -> None:
self.parse_args(args) self.parse_args(args)
@@ -58,7 +56,7 @@ class KleinanzeigenBot(SeleniumMixin):
case "help": case "help":
self.show_help() self.show_help()
case "version": case "version":
print(VERSION) print(self.get_version())
case "verify": case "verify":
self.configure_file_logging() self.configure_file_logging()
self.load_config() self.load_config()
@@ -85,6 +83,8 @@ class KleinanzeigenBot(SeleniumMixin):
def show_help(self) -> None: def show_help(self) -> None:
if is_frozen(): if is_frozen():
exe = sys.argv[0] exe = sys.argv[0]
elif os.getenv("PDM_PROJECT_ROOT", ""):
exe = "pdm run app"
else: else:
exe = "python -m kleinanzeigen_bot" exe = "python -m kleinanzeigen_bot"
@@ -143,6 +143,8 @@ class KleinanzeigenBot(SeleniumMixin):
self.file_log.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) self.file_log.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
LOG_ROOT.addHandler(self.file_log) LOG_ROOT.addHandler(self.file_log)
LOG.info("App version: %s", self.get_version())
def load_ads(self, exclude_inactive = True, exclude_undue = True) -> Iterable[Dict[str, Any]]: def load_ads(self, exclude_inactive = True, exclude_undue = True) -> Iterable[Dict[str, Any]]:
LOG.info("Searching for ad files...") LOG.info("Searching for ad files...")

View File

@@ -2,9 +2,8 @@
Copyright (C) 2022 Sebastian Thomschke and contributors Copyright (C) 2022 Sebastian Thomschke and contributors
SPDX-License-Identifier: AGPL-3.0-or-later SPDX-License-Identifier: AGPL-3.0-or-later
""" """
import logging, os, shutil, sys, tempfile import logging, os, shutil, sys
from typing import Any, Callable, Dict, Final, Iterable, Tuple from typing import Any, Callable, Dict, Final, Iterable, Tuple
from importlib.resources import read_text as get_resource_as_string
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.common.exceptions import NoSuchElementException, TimeoutException
@@ -22,7 +21,7 @@ from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager from webdriver_manager.microsoft import EdgeChromiumDriverManager
from webdriver_manager.utils import ChromeType from webdriver_manager.utils import ChromeType
from .utils import ensure, is_frozen, pause from .utils import ensure, pause
LOG:Final[logging.Logger] = logging.getLogger("kleinanzeigen_bot.selenium_mixin") LOG:Final[logging.Logger] = logging.getLogger("kleinanzeigen_bot.selenium_mixin")
@@ -34,10 +33,6 @@ class SeleniumMixin:
self.browser_binary_location:str = None self.browser_binary_location:str = None
self.webdriver:WebDriver = None self.webdriver:WebDriver = None
def __del__(self):
if getattr(self, 'cacertfile', None):
os.remove(self.cacertfile)
def create_webdriver_session(self) -> None: def create_webdriver_session(self) -> None:
LOG.info("Creating WebDriver session...") LOG.info("Creating WebDriver session...")
@@ -68,27 +63,6 @@ class SeleniumMixin:
LOG.info(" -> Chrome binary location: %s", self.browser_binary_location) LOG.info(" -> Chrome binary location: %s", self.browser_binary_location)
return browser_options return browser_options
# if run via py2exe fix resource lookup
if is_frozen():
import pathlib # pylint: disable=import-outside-toplevel
if not os.getenv("REQUESTS_CA_BUNDLE", None) or not os.path.exists(os.getenv("REQUESTS_CA_BUNDLE", None)):
with tempfile.NamedTemporaryFile(delete = False) as tmp:
LOG.debug("Writing cacert file to [%s]...", tmp.name)
tmp.write(get_resource_as_string("certifi", "cacert.pem").encode('utf-8'))
self.cacertfile = tmp.name
os.environ['REQUESTS_CA_BUNDLE'] = self.cacertfile
read_text_orig = pathlib.Path.read_text
def read_text_new(self, encoding = None, errors = None):
path = str(self)
if "selenium_stealth" in path:
return get_resource_as_string("selenium_stealth", self.name)
return read_text_orig(self, encoding, errors)
pathlib.Path.read_text = read_text_new
# check if a chrome driver is present already # check if a chrome driver is present already
if shutil.which(DEFAULT_CHROMEDRIVER_PATH): if shutil.which(DEFAULT_CHROMEDRIVER_PATH):
self.webdriver = webdriver.Chrome(options = init_browser_options(webdriver.ChromeOptions())) self.webdriver = webdriver.Chrome(options = init_browser_options(webdriver.ChromeOptions()))

1062
pdm.lock generated Normal file

File diff suppressed because it is too large Load Diff

79
pyinstaller.spec Normal file
View File

@@ -0,0 +1,79 @@
# -*- mode: python ; coding: utf-8 -*-
"""
Copyright (C) 2022 Sebastian Thomschke and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
PyInstaller config file, see https://pyinstaller.readthedocs.io/en/stable/spec-files.html
"""
from PyInstaller.utils.hooks import copy_metadata, collect_data_files
datas = [
* copy_metadata('kleinanzeigen_bot'), # required to get version info
* collect_data_files("kleinanzeigen_bot"), # embeds *.yaml files
* collect_data_files("selenium_stealth"), # embeds *.js files
]
block_cipher = None
analysis = Analysis(
['kleinanzeigen_bot/__main__.py'],
pathex = [],
binaries = [],
datas = datas,
hiddenimports = ['pkg_resources'],
hookspath = [],
hooksconfig = {},
runtime_hooks = [],
excludes = [
"_aix_support",
"_osx_support",
"argparse",
"backports",
"bz2",
"cryptography.hazmat",
"distutils",
"doctest",
"ftplib",
"lzma",
"pep517",
"pdb",
"pip",
"pydoc",
"pydoc_data",
"optparse",
"setuptools",
"six",
"statistics",
"test",
"unittest",
"xml.sax"
],
win_no_prefer_redirects = False,
win_private_assemblies = False,
cipher = block_cipher,
noarchive = False
)
pyz = PYZ(analysis.pure, analysis.zipped_data, cipher = block_cipher)
import shutil
exe = EXE(pyz,
analysis.scripts,
analysis.binaries,
analysis.zipfiles,
analysis.datas,
[],
name = 'kleinanzeigen-bot',
debug = False,
bootloader_ignore_signals = False,
strip = shutil.which("strip") is not None,
upx = shutil.which("upx") is not None,
upx_exclude = [],
runtime_tmpdir = None,
console = True,
disable_windowed_traceback = False,
target_arch = None,
codesign_identity = None,
entitlements_file = None
)

View File

@@ -1,10 +1,81 @@
# https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/ # https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/
[build-system]
requires = ["pdm-pep517"]
build-backend = "pdm.pep517.api"
[project]
name = "kleinanzeigen-bot"
dynamic = ["version"]
description = "Command line tool to publish ads on ebay-kleinanzeigen.de"
readme = "README.md"
authors = [
{name = "sebthom", email = "sebthom@users.noreply.github.com"},
]
license = {text = "AGPL-3.0-or-later"}
classifiers = [ # https://pypi.org/classifiers/
"Development Status :: 4 - Beta",
"Environment :: Console",
"Operating System :: OS Independent",
"Intended Audience :: End Users/Desktop",
"Topic :: Office/Business",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Programming Language :: Python :: 3.10"
]
requires-python = ">=3.10,<3.11" # <3.11 to get newer versions of pyinstaller
dependencies = [
"coloredlogs~=15.0",
"inflect~=5.3",
"ruamel.yaml~=0.17",
"pywin32==303; sys_platform == 'win32'",
"selenium~=4.1",
"selenium_stealth~=1.0",
"webdriver_manager~=3.5"]
[project.urls]
homepage = "https://github.com/kleinanzeigen-bot/kleinanzeigen-bot"
repository = "https://github.com/kleinanzeigen-bot/kleinanzeigen-bot"
documentation = "https://github.com/kleinanzeigen-bot/kleinanzeigen-bot/README.md"
[project.optional-dependencies]
[project.scripts]
# https://www.python.org/dev/peps/pep-0621/#entry-points
# https://blogs.thebitx.com/index.php/2021/09/02/pybites-how-to-package-and-deploy-cli-applications-with-python-pypa-setuptools-build/
kleinanzeigen_bot = 'kleinanzeigen_bot:main'
#####################
# pdm https://github.com/pdm-project/pdm/
#####################
[tool.pdm]
version = {use_scm = true}
[tool.pdm.dev-dependencies]
dev = [
"bandit~=1.7",
"pytest~=6.2",
"pyinstaller~=4.8",
"psutil",
"pylint~=2.12",
"mypy~=0.931",
]
[tool.pdm.scripts]
app = "python -m kleinanzeigen_bot"
bandit = "bandit -c pyproject.toml -r kleinanzeigen_bot"
pyinstaller = "pyinstaller pyinstaller.spec --clean"
pylint = "pylint kleinanzeigen_bot"
pytest = "python -m pytest -v"
##################### #####################
# bandit https://github.com/PyCQA/bandit # bandit https://github.com/PyCQA/bandit
##################### #####################
[tool.bandit] [tool.bandit]
exclude = ["*/.eggs/*"] # broken :-( https://github.com/PyCQA/bandit/issues/657
##################### #####################
@@ -46,9 +117,10 @@ max-locals = 30
max-returns = 10 max-returns = 10
max-statements = 70 max-statements = 70
##################### #####################
# pytest # pytest
##################### #####################
[tool.pytest.ini_options] [tool.pytest.ini_options]
# https://docs.pytest.org/en/stable/reference.html#confval-addopts # https://docs.pytest.org/en/stable/reference.html#confval-addopts
addopts = "-p no:cacheprovider --doctest-modules --ignore=kleinanzeigen_bot/__main__.py" addopts = "-p no:cacheprovider --doctest-modules --ignore=__pypackages__ --ignore=kleinanzeigen_bot/__main__.py"

View File

@@ -1,6 +0,0 @@
bandit~=1.7.1
py2exe~=0.11.0.1; sys_platform == 'win32'
pylint~=2.12.2
pytest~=6.2.5
setuptools~=60.5.0
wheel~=0.37.1

View File

@@ -1,7 +0,0 @@
coloredlogs~=15.0.1
inflect~=5.3.0
ruamel.yaml~=0.17.20
pywin32==303; sys_platform == 'win32'
selenium~=4.1.0
selenium_stealth~=1.0.6
webdriver_manager~=3.5.2

161
setup.py
View File

@@ -1,161 +0,0 @@
#!/usr/bin/env python3
"""
Copyright (C) 2022 Sebastian Thomschke and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
"""
import sys, warnings
import setuptools
warnings.filterwarnings("ignore", message = "setup_requires is deprecated", category = setuptools.SetuptoolsDeprecationWarning)
warnings.filterwarnings("ignore", message = "setuptools.installer is deprecated", category = setuptools.SetuptoolsDeprecationWarning)
setup_args = {}
if "py2exe" in sys.argv:
import importlib.resources, glob, os, py2exe, zipfile
# py2exe config https://www.py2exe.org/index.cgi/ListOfOptions
setup_args["options"] = {
"py2exe": {
"bundle_files": 1, # 1 = include the python runtime
"compressed": True,
"optimize": 2,
"includes": [
"kleinanzeigen_bot"
],
"excludes": [
"_aix_support",
"_osx_support",
"argparse",
"backports",
"bz2",
"cryptography.hazmat",
"distutils",
"doctest",
"ftplib",
"lzma",
"pep517",
"pip",
"pydoc",
"pydoc_data",
"optparse",
"pyexpat",
"six",
"statistics",
"test",
"unittest",
"xml.sax"
]
}
}
setup_args["console"] = [{
"script": "kleinanzeigen_bot/__main__.py",
"dest_base": "kleinanzeigen-bot",
}]
setup_args["zipfile"] = None
#
# embedding required DLLs directly into the exe
#
# http://www.py2exe.org/index.cgi/OverridingCriteraForIncludingDlls
bundle_dlls = ("libcrypto", "libffi", "libssl")
orig_determine_dll_type = py2exe.dllfinder.DllFinder.determine_dll_type
def determine_dll_type(self, dll_filepath):
basename = os.path.basename(dll_filepath)
if basename.startswith(bundle_dlls):
return "EXT"
return orig_determine_dll_type(self, dll_filepath)
py2exe.dllfinder.DllFinder.determine_dll_type = determine_dll_type
#
# embedding required resource files directly into the exe
#
files_to_embed = [
("kleinanzeigen_bot/resources", "kleinanzeigen_bot/resources/*.yaml"),
("certifi", importlib.resources.path("certifi", "cacert.pem")),
("selenium_stealth", os.path.dirname(importlib.resources.path("selenium_stealth.js", "util.js")))
]
orig_copy_files = py2exe.runtime.Runtime.copy_files
def embed_files(self, destdir):
orig_copy_files(self, destdir)
libpath = os.path.join(destdir, "kleinanzeigen-bot.exe")
with zipfile.ZipFile(libpath, "a", zipfile.ZIP_DEFLATED if self.options.compress else zipfile.ZIP_STORED) as arc:
for target, source in files_to_embed:
if os.path.isdir(source):
for file in os.listdir(source):
if self.options.verbose:
print(f"Embedding file {source}\\{file} in {libpath}")
arc.write(os.path.join(source, file), target + "/" + file)
elif isinstance(source, str):
for file in glob.glob(source, root_dir = os.getcwd(), recursive = True):
if self.options.verbose:
print(f"Embedding file {file} in {libpath}")
arc.write(file, target + "/" + os.path.basename(file))
else:
if self.options.verbose:
print(f"Embedding file {source} in {libpath}")
arc.write(source, target + "/" + os.path.basename(source))
os.remove(os.path.join(destdir, "cacert.pem")) # file was embedded
py2exe.runtime.Runtime.copy_files = embed_files
#
# use best zip compression level 9
#
from zipfile import ZipFile
class ZipFileExt(ZipFile):
def __init__(self, file, mode = "r", compression = zipfile.ZIP_STORED):
super().__init__(file, mode, compression, compresslevel = 9)
py2exe.runtime.zipfile.ZipFile = ZipFileExt
def load_requirements(filepath:str):
with open(filepath, encoding = "utf-8") as fd:
return [nonempty for line in fd if (nonempty := line.strip())]
setuptools.setup(
name = "kleinanzeigen-bot",
use_scm_version = {
"write_to": "kleinanzeigen_bot/version.py",
},
packages = setuptools.find_packages(),
package_data = {"kleinanzeigen_bot": ["resources/*.yaml"]},
# https://docs.python.org/3/distutils/setupscript.html#additional-meta-data
author = "The kleinanzeigen-bot authors",
author_email = "n/a",
url = "https://github.com/kleinanzeigen-bot/kleinanzeigen-bot",
description = "Command line tool to publish ads on ebay-kleinanzeigen.de",
license = "GNU AGPL 3.0+",
classifiers = [ # https://pypi.org/classifiers/
"Development Status :: 4 - Beta",
"Environment :: Console",
"Operating System :: OS Independent",
"Intended Audience :: End Users/Desktop",
"Topic :: Office/Business",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Programming Language :: Python :: 3.10",
],
python_requires = ">=3.10",
install_requires = load_requirements("requirements.txt"),
extras_require = {
"dev": load_requirements("requirements-dev.txt")
},
setup_requires = [
"setuptools_scm"
],
** setup_args
)

4
tests/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
"""
Copyright (C) 2022 Sebastian Thomschke and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
"""

13
tests/test_utils.py Normal file
View File

@@ -0,0 +1,13 @@
"""
Copyright (C) 2022 Sebastian Thomschke and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
"""
import time
from kleinanzeigen_bot import utils
def test_pause():
start = time.time()
utils.pause(100, 100)
elapsed = 1000 * (time.time() - start)
assert 99 < elapsed < 110