mirror of
https://github.com/BoPeng/ai-marketplace-monitor.git
synced 2025-12-23 22:28:18 -05:00
Switch from poetry to uv (#203)
* Switch from poetry to uv * Suppress an info message (close #204) * Fix end of line for markdown files
This commit is contained in:
9
.github/workflows/pre-commit-autoupdate.yml
vendored
9
.github/workflows/pre-commit-autoupdate.yml
vendored
@@ -20,16 +20,15 @@ jobs:
|
||||
- name: Install system deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install poetry
|
||||
poetry config virtualenvs.in-project true
|
||||
poetry install --no-root --only dev --only linters --sync
|
||||
pip install uv
|
||||
uv sync --extra dev --extra linters
|
||||
|
||||
- name: Run autoupdate
|
||||
run: poetry run pre-commit autoupdate
|
||||
run: uv run pre-commit autoupdate
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run pre-commit
|
||||
run: poetry run pre-commit run --all-files
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
- uses: peter-evans/create-pull-request@v7.0.8
|
||||
with:
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -23,12 +23,11 @@ jobs:
|
||||
- name: Install system deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install poetry
|
||||
poetry config virtualenvs.in-project true
|
||||
pip install uv
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
poetry build --ansi
|
||||
uv build
|
||||
|
||||
- name: Publish package on PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||
|
||||
23
.github/workflows/tests.yml
vendored
23
.github/workflows/tests.yml
vendored
@@ -21,13 +21,12 @@ jobs:
|
||||
- name: Install system deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install poetry
|
||||
poetry config virtualenvs.in-project true
|
||||
poetry install --no-root --only dev --only linters --sync
|
||||
pip install uv
|
||||
uv sync --extra dev --extra linters
|
||||
|
||||
- name: Linting
|
||||
shell: bash
|
||||
run: poetry run pre-commit run --all-files
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
tests:
|
||||
needs: linting
|
||||
@@ -49,18 +48,17 @@ jobs:
|
||||
- name: Install system deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install nox-poetry==1.1.0
|
||||
pip install poetry==1.8.5
|
||||
poetry config virtualenvs.in-project true
|
||||
pip install nox
|
||||
pip install uv
|
||||
|
||||
- name: Run mypy with nox
|
||||
shell: bash
|
||||
run: nox --force-color -s mypy-${{ matrix.python-version }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
- name: Install dependencies and Playwright Browsers
|
||||
run: |
|
||||
pip install playwright
|
||||
playwright install --with-deps # Ensures browsers and dependencies are installed
|
||||
uv sync --extra dev --extra test
|
||||
uv run playwright install --with-deps
|
||||
|
||||
- name: Run tests with nox
|
||||
shell: bash
|
||||
@@ -93,9 +91,8 @@ jobs:
|
||||
# - name: Install system deps
|
||||
# shell: bash
|
||||
# run: |
|
||||
# pip install nox-poetry==1.1.0
|
||||
# pip install poetry==1.8.5
|
||||
# poetry config virtualenvs.in-project true
|
||||
# pip install nox==1.1.0
|
||||
# pip install uv==1.8.5
|
||||
|
||||
# - name: Download coverage data
|
||||
# uses: actions/download-artifact@v4.1.8
|
||||
|
||||
@@ -35,8 +35,8 @@ repos:
|
||||
rev: 5295f87c0e261da61a7b919fc754e3a77edd98a7
|
||||
hooks:
|
||||
- id: validate-cff
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 1.8.3
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
# uv version.
|
||||
rev: 0.7.19
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-install
|
||||
- id: uv-lock
|
||||
|
||||
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.6]
|
||||
|
||||
- Fix searching across regions.
|
||||
- Switch from `poetry` to `uv` for development.
|
||||
|
||||
## [0.9.5]
|
||||
|
||||
- [issue 155](https://github.com/BoPeng/ai-marketplace-monitor/issues/155) Fix output of pushbullet
|
||||
|
||||
@@ -13,10 +13,10 @@ We take our open source community seriously and hold ourselves and other contrib
|
||||
|
||||
### Requirements
|
||||
|
||||
We use `poetry` to manage and install dependencies. [Poetry](https://python-poetry.org/) provides a custom installer that will install `poetry` isolated from the rest of your system.
|
||||
We use `uv` to manage and install dependencies. [uv](https://docs.astral.sh/uv/) is a fast Python package manager that can be installed with:
|
||||
|
||||
```
|
||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python -
|
||||
pip install uv
|
||||
```
|
||||
|
||||
We'll also need `nox` for automated testing in multiple Python environments so [install that too](https://nox.thea.codes/en/stable/).
|
||||
@@ -24,11 +24,11 @@ We'll also need `nox` for automated testing in multiple Python environments so [
|
||||
To install the local development requirements inside a virtual environment run:
|
||||
|
||||
```
|
||||
$ poetry install
|
||||
$ poetry run inv install-hooks
|
||||
$ uv sync --all-extras
|
||||
$ uv run inv install-hooks
|
||||
```
|
||||
|
||||
> For more information about `poetry` check the [docs](https://python-poetry.org/docs/).
|
||||
> For more information about `uv` check the [docs](https://docs.astral.sh/uv/).
|
||||
|
||||
We use [invoke](http://www.pyinvoke.org/) to wrap up some useful tasks like formatting, linting, testing and more.
|
||||
|
||||
@@ -59,7 +59,7 @@ git checkout dev
|
||||
Then install the tool from source code with command
|
||||
|
||||
```sh
|
||||
poetry install
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
79
MIGRATION_TO_UV.md
Normal file
79
MIGRATION_TO_UV.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Migration from Poetry to uv
|
||||
|
||||
This project has been migrated from Poetry to uv for faster dependency management and better performance.
|
||||
|
||||
## For Contributors
|
||||
|
||||
If you were previously contributing to this project using Poetry, here's how to migrate:
|
||||
|
||||
### 1. Remove Poetry artifacts
|
||||
```bash
|
||||
# Remove the old virtual environment (if using poetry's default location)
|
||||
rm -rf .venv
|
||||
# Remove poetry.lock (now replaced by uv.lock)
|
||||
rm poetry.lock
|
||||
```
|
||||
|
||||
### 2. Install uv
|
||||
```bash
|
||||
pip install uv
|
||||
```
|
||||
|
||||
### 3. Set up the development environment
|
||||
```bash
|
||||
# Install all dependencies including development extras
|
||||
uv sync --all-extras
|
||||
|
||||
# Install pre-commit hooks
|
||||
uv run inv install-hooks
|
||||
```
|
||||
|
||||
### 4. Common command translations
|
||||
|
||||
| Poetry Command | uv Equivalent |
|
||||
| -------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `poetry install` | `uv sync` |
|
||||
| `poetry add package` | `uv add package` |
|
||||
| `poetry add --group dev package` | `uv add --dev package` |
|
||||
| `poetry run command` | `uv run command` |
|
||||
| `poetry shell` | `source .venv/bin/activate` (Linux/Mac) or `.venv\Scripts\activate` (Windows) |
|
||||
| `poetry build` | `uv build` |
|
||||
| `poetry publish` | `uv publish` |
|
||||
|
||||
### 5. Running tasks
|
||||
All invoke tasks now use uv instead of poetry:
|
||||
```bash
|
||||
# Run tests
|
||||
uv run inv tests
|
||||
|
||||
# Format code
|
||||
uv run inv format
|
||||
|
||||
# Run linting
|
||||
uv run inv lint
|
||||
|
||||
# Type checking
|
||||
uv run inv mypy
|
||||
```
|
||||
|
||||
## For End Users
|
||||
|
||||
If you were installing the package from source using Poetry, now use:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/BoPeng/ai-marketplace-monitor
|
||||
cd ai-marketplace-monitor
|
||||
uv sync
|
||||
```
|
||||
|
||||
The published package on PyPI remains the same:
|
||||
```bash
|
||||
pip install ai-marketplace-monitor
|
||||
```
|
||||
|
||||
## Benefits of uv
|
||||
|
||||
- **Faster**: uv is significantly faster than Poetry for dependency resolution and installation
|
||||
- **Better caching**: More efficient caching mechanism
|
||||
- **Simpler**: Fewer configuration files and simpler setup
|
||||
- **Standard**: Uses standard Python packaging (pyproject.toml) without Poetry-specific extensions
|
||||
@@ -128,7 +128,7 @@ playwright install
|
||||
|
||||
### Set up a notification method (optional)
|
||||
|
||||
If you would like to receive notification from your phone via PushBullet
|
||||
If you would like to receive notification from your phone
|
||||
|
||||
- Sign up for [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/) or [Ntfy](https://ntfy.sh/)
|
||||
- Install the app on your phone
|
||||
|
||||
26
noxfile.py
26
noxfile.py
@@ -3,19 +3,17 @@
|
||||
import platform
|
||||
|
||||
import nox
|
||||
from nox_poetry import Session, session
|
||||
from nox import Session
|
||||
|
||||
nox.options.sessions = ["tests", "mypy"]
|
||||
python_versions = ["3.10", "3.11", "3.12"]
|
||||
|
||||
|
||||
@session(python=python_versions)
|
||||
@nox.session(python=python_versions)
|
||||
def tests(session: Session) -> None:
|
||||
"""Run the test suite."""
|
||||
session.install(".")
|
||||
session.install(
|
||||
"invoke", "pytest", "xdoctest", "coverage[toml]", "pytest-cov", "pytest-playwright"
|
||||
)
|
||||
session.run("uv", "sync", "--extra", "test", external=True)
|
||||
session.install("invoke")
|
||||
try:
|
||||
session.run(
|
||||
"inv",
|
||||
@@ -29,24 +27,26 @@ def tests(session: Session) -> None:
|
||||
session.notify("coverage")
|
||||
|
||||
|
||||
@session(python=python_versions)
|
||||
@nox.session(python=python_versions)
|
||||
def coverage(session: Session) -> None:
|
||||
"""Produce the coverage report."""
|
||||
args = session.posargs if session.posargs and len(session._runner.manifest) == 1 else []
|
||||
session.install("invoke", "coverage[toml]")
|
||||
session.run("uv", "sync", "--extra", "test", external=True)
|
||||
session.install("invoke")
|
||||
session.run("inv", "coverage", *args)
|
||||
|
||||
|
||||
@session(python=python_versions)
|
||||
@nox.session(python=python_versions)
|
||||
def mypy(session: Session) -> None:
|
||||
"""Type-check using mypy."""
|
||||
session.install(".")
|
||||
session.install("invoke", "mypy")
|
||||
session.run("uv", "sync", "--extra", "typing", external=True)
|
||||
session.install("invoke")
|
||||
session.run("inv", "mypy")
|
||||
|
||||
|
||||
@session(python="3.12")
|
||||
@nox.session(python="3.12")
|
||||
def security(session: Session) -> None:
|
||||
"""Scan dependencies for insecure packages."""
|
||||
session.install("invoke", "safety")
|
||||
session.run("uv", "sync", "--extra", "security", external=True)
|
||||
session.install("invoke")
|
||||
session.run("inv", "security")
|
||||
|
||||
2963
poetry.lock
generated
2963
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
126
pyproject.toml
126
pyproject.toml
@@ -1,12 +1,10 @@
|
||||
[tool.poetry]
|
||||
[project]
|
||||
name = "ai-marketplace-monitor"
|
||||
version = "0.9.5"
|
||||
description = "An AI-based tool for monitoring facebook marketplace"
|
||||
authors = ["Bo Peng <ben.bob@gmail.com>"]
|
||||
authors = [{name = "Bo Peng", email = "ben.bob@gmail.com"}]
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/BoPeng/ai-marketplace-monitor"
|
||||
repository = "https://github.com/BoPeng/ai-marketplace-monitor"
|
||||
documentation = "https://ai-marketplace-monitor.readthedocs.io"
|
||||
requires-python = ">=3.10"
|
||||
keywords = ["ai-marketplace-monitor"]
|
||||
classifiers = [
|
||||
"Development Status :: 2 - Pre-Alpha",
|
||||
@@ -14,66 +12,72 @@ classifiers = [
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||
"Natural Language :: English",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
"typer>=0.15.1,<0.17.0",
|
||||
"playwright>=1.41.0",
|
||||
"rich>=13.7.0",
|
||||
"pushbullet.py>=0.12.0",
|
||||
"diskcache>=5.6.3",
|
||||
"watchdog>=4.0.0",
|
||||
"openai>=1.24.0",
|
||||
"parsedatetime>=2.5",
|
||||
"humanize>=4.0.0",
|
||||
"schedule>=1.2.2",
|
||||
"inflect>=7.0.0",
|
||||
"pynput>=1.7.0",
|
||||
"pillow>=10.0.0",
|
||||
"jinja2>=3.0.0",
|
||||
"pyparsing>=3.1.0",
|
||||
"requests>=2.30.0",
|
||||
"CurrencyConverter>=0.18.0",
|
||||
"tomli==2.2.1; python_version < '3.11'",
|
||||
"safety>=3.5.2",
|
||||
"pip-audit>=2.9.0",
|
||||
]
|
||||
|
||||
[tool.poetry.urls]
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/BoPeng/ai-marketplace-monitor"
|
||||
Repository = "https://github.com/BoPeng/ai-marketplace-monitor"
|
||||
Documentation = "https://ai-marketplace-monitor.readthedocs.io"
|
||||
"Bug Tracker" = "https://github.com/BoPeng/ai-marketplace-monitor/issues"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
ai-marketplace-monitor = 'ai_marketplace_monitor.cli:app'
|
||||
[project.scripts]
|
||||
ai-marketplace-monitor = "ai_marketplace_monitor.cli:app"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10"
|
||||
typer = { extras = ["all"], version = ">=0.15.1,<0.17.0" }
|
||||
playwright = ">=1.41.0"
|
||||
rich = ">=13.7.0"
|
||||
"pushbullet.py" = ">=0.12.0"
|
||||
diskcache = ">=5.6.3"
|
||||
watchdog = ">=4.0.0"
|
||||
openai = ">=1.24.0"
|
||||
parsedatetime = ">=2.5"
|
||||
humanize = ">=4.0.0"
|
||||
schedule = ">=1.2.2"
|
||||
inflect = ">=7.0.0"
|
||||
pynput = ">=1.7.0"
|
||||
pillow = ">=10.0.0"
|
||||
jinja2 = ">=3.0.0"
|
||||
pyparsing = ">=3.1.0"
|
||||
requests = ">=2.30.0"
|
||||
CurrencyConverter = ">=0.18.0"
|
||||
tomli = { version = "2.2.1", markers = "python_version < '3.11'" }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pre-commit = "^4.0.1"
|
||||
invoke = "^2.2.0"
|
||||
bump2version = "^1.0.1"
|
||||
watchdog = { version = "^6.0.0", extras = ["watchmedo"] }
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.3.3"
|
||||
xdoctest = "^1.2.0"
|
||||
coverage = { version = "^7.6.7", extras = ["toml"] }
|
||||
pytest-cov = "^6.0.0"
|
||||
pytest-playwright = "^0.7.0"
|
||||
|
||||
[tool.poetry.group.linters.dependencies]
|
||||
isort = ">=5.13.2,<7.0.0"
|
||||
black = ">=24.10,<26.0"
|
||||
ruff = ">=0.9.2,<0.13.0"
|
||||
|
||||
[tool.poetry.group.security.dependencies]
|
||||
safety = "^3.2.11"
|
||||
|
||||
[tool.poetry.group.typing.dependencies]
|
||||
mypy = "^1.13.0"
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
sphinx = "^8.1.3"
|
||||
recommonmark = "^0.7.1"
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pre-commit>=4.0.1",
|
||||
"invoke>=2.2.0",
|
||||
"bump2version>=1.0.1",
|
||||
"watchdog[watchmedo]>=6.0.0",
|
||||
"pip-audit>=2.9.0",
|
||||
]
|
||||
test = [
|
||||
"pytest>=8.3.3",
|
||||
"xdoctest>=1.2.0",
|
||||
"coverage[toml]>=7.6.7",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-playwright>=0.7.0",
|
||||
]
|
||||
linters = [
|
||||
"isort>=5.13.2,<7.0.0",
|
||||
"black>=24.10,<26.0",
|
||||
"ruff>=0.9.2,<0.13.0",
|
||||
]
|
||||
security = [
|
||||
"safety>=3.2.11",
|
||||
]
|
||||
typing = [
|
||||
"mypy>=1.13.0",
|
||||
]
|
||||
docs = [
|
||||
"sphinx>=8.1.3",
|
||||
"recommonmark>=0.7.1",
|
||||
]
|
||||
|
||||
[tool.coverage.paths]
|
||||
source = ["src", "*/site-packages"]
|
||||
@@ -175,7 +179,7 @@ include_trailing_comma = true
|
||||
force_grid_wrap = 0
|
||||
use_parentheses = true
|
||||
line_length = 99
|
||||
known_third_party = ["invoke", "nox", "nox_poetry"]
|
||||
known_third_party = ["invoke", "nox"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 99
|
||||
@@ -186,7 +190,7 @@ warn_return_any = false
|
||||
warn_unused_configs = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["pytest.*", "invoke.*", "nox.*", "nox_poetry.*"]
|
||||
module = ["pytest.*", "invoke.*", "nox.*"]
|
||||
allow_redefinition = false
|
||||
check_untyped_defs = true
|
||||
ignore_errors = false
|
||||
@@ -201,5 +205,5 @@ warn_unreachable = true
|
||||
warn_no_return = true
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
@@ -271,7 +271,7 @@ class OpenAIBackend(AIBackend):
|
||||
res: AIResponse | None = AIResponse.from_cache(listing, item_config, marketplace_config)
|
||||
if res is not None:
|
||||
if self.logger:
|
||||
self.logger.info(
|
||||
self.logger.debug(
|
||||
f"""{hilight("[AI]", res.style)} {self.config.name} previously concluded {hilight(f"{res.conclusion} ({res.score}): {res.comment}", res.style)} for listing {hilight(listing.title)}."""
|
||||
)
|
||||
return res
|
||||
|
||||
@@ -321,6 +321,7 @@ locale = "Spanish"
|
||||
'Location is approximate' = 'La ubicación es aproximada'
|
||||
"About this vehicle" = 'Descripción del vendedor'
|
||||
"Seller's description" = 'Información sobre este vehículo'
|
||||
"Browse Marketplace" = 'Explorar Marketplace'
|
||||
|
||||
[translation.zh]
|
||||
locale = "Chinese"
|
||||
@@ -331,3 +332,15 @@ locale = "Chinese"
|
||||
'Location is approximate' = '我们只提供大概位置'
|
||||
"About this vehicle" = "车辆信息"
|
||||
"Seller's description" = "卖家描述"
|
||||
"Browse Marketplace" = '浏览 Marketplace'
|
||||
|
||||
[translation.sv]
|
||||
locale = "Swedish"
|
||||
'Collection of Marketplace items' = 'Samling av Marketplace-objekt'
|
||||
'Condition' = 'Skick'
|
||||
'Description' = 'Beskrivning'
|
||||
'Details' = 'Detaljer'
|
||||
'Location is approximate' = 'Platsen är ungefärlig'
|
||||
"About this vehicle" = "Om detta fordon"
|
||||
"Seller's description" = "Säljarens beskrivning"
|
||||
"Browse Marketplace" = 'Bläddra på Marketplace'
|
||||
|
||||
@@ -473,30 +473,19 @@ class FacebookMarketplace(Marketplace):
|
||||
f"""{hilight(item_config.name)} from {hilight(cname or city)}"""
|
||||
+ (f" with radius={radius}" if radius else " with default radius")
|
||||
)
|
||||
retries = 0
|
||||
while True:
|
||||
self.goto_url(
|
||||
marketplace_url + "&".join([f"query={quote(search_phrase)}", *options])
|
||||
)
|
||||
|
||||
found_listings = FacebookSearchResultPage(
|
||||
self.page, self.translator, self.logger
|
||||
).get_listings()
|
||||
time.sleep(5)
|
||||
if found_listings:
|
||||
break
|
||||
if retries > 5:
|
||||
if self.logger:
|
||||
self.logger.error(
|
||||
f"""{hilight("[Search]", "fail")} Failed to get search results for {search_phrase}"""
|
||||
)
|
||||
break
|
||||
else:
|
||||
retries += 1
|
||||
if self.logger:
|
||||
self.logger.debug(
|
||||
f"""{hilight("[Search]", "info")} Retrying to get search results for {search_phrase}"""
|
||||
)
|
||||
self.goto_url(
|
||||
marketplace_url + "&".join([f"query={quote(search_phrase)}", *options])
|
||||
)
|
||||
|
||||
found_listings = FacebookSearchResultPage(
|
||||
self.page, self.translator, self.logger
|
||||
).get_listings()
|
||||
time.sleep(5)
|
||||
if self.logger:
|
||||
self.logger.error(
|
||||
f"""{hilight("[Search]", "fail")} Failed to get search results for {search_phrase} from {city}"""
|
||||
)
|
||||
|
||||
counter.increment(CounterItem.SEARCH_PERFORMED, item_config.name)
|
||||
|
||||
@@ -688,6 +677,19 @@ class FacebookSearchResultPage(WebPage):
|
||||
return valid_listings
|
||||
|
||||
def get_listings(self: "FacebookSearchResultPage") -> List[Listing]:
|
||||
# if no result is found
|
||||
btn = self.page.locator(f"""span:has-text('{self.translator("Browse Marketplace")}')""")
|
||||
if btn.count() > 0:
|
||||
if self.logger:
|
||||
msg = self._parent_with_cond(
|
||||
btn.first,
|
||||
lambda x: len(x) == 3
|
||||
and self.translator("Browse Marketplace") in (x[-1].text_content() or ""),
|
||||
1,
|
||||
)
|
||||
self.logger.info(f'{hilight("[Retrieve]", "dim")} {msg}')
|
||||
return []
|
||||
|
||||
# find the grid box
|
||||
try:
|
||||
valid_listings = (
|
||||
@@ -1138,6 +1140,6 @@ def parse_listing(
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
# try next page layout
|
||||
# try next page ayout
|
||||
continue
|
||||
return None
|
||||
|
||||
@@ -23,7 +23,7 @@ class RegionConfig(BaseConfig):
|
||||
|
||||
def handle_radius(self: "RegionConfig") -> None:
|
||||
if isinstance(self.radius, int):
|
||||
self.radius = [self.radius]
|
||||
self.radius = [self.radius] * len(self.search_city)
|
||||
elif not self.radius:
|
||||
self.radius = [500] * len(self.search_city)
|
||||
elif len(self.radius) != len(self.search_city):
|
||||
|
||||
43
tasks.py
43
tasks.py
@@ -3,7 +3,9 @@
|
||||
Execute 'invoke --list' for guidance on using Invoke
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import tempfile
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
@@ -76,42 +78,49 @@ def clean(c: Context) -> None:
|
||||
@task()
|
||||
def install_hooks(c: Context) -> None:
|
||||
"""Install pre-commit hooks."""
|
||||
_run(c, "poetry run pre-commit install")
|
||||
_run(c, "uv run pre-commit install")
|
||||
|
||||
|
||||
@task()
|
||||
def hooks(c: Context) -> None:
|
||||
"""Run pre-commit hooks."""
|
||||
_run(c, "poetry run pre-commit run --all-files")
|
||||
_run(c, "uv run pre-commit run --all-files")
|
||||
|
||||
|
||||
@task(name="format", help={"check": "Checks if source is formatted without applying changes"})
|
||||
def format_(c: Context, check: bool = False) -> None:
|
||||
"""Format code."""
|
||||
isort_options = ["--check-only", "--diff"] if check else []
|
||||
_run(c, f"poetry run isort {' '.join(isort_options)} {PYTHON_TARGETS_STR}")
|
||||
_run(c, f"uv run isort {' '.join(isort_options)} {PYTHON_TARGETS_STR}")
|
||||
black_options = ["--diff", "--check"] if check else ["--quiet"]
|
||||
_run(c, f"poetry run black {' '.join(black_options)} {PYTHON_TARGETS_STR}")
|
||||
_run(c, f"uv run black {' '.join(black_options)} {PYTHON_TARGETS_STR}")
|
||||
|
||||
|
||||
@task()
|
||||
def ruff(c: Context) -> None:
|
||||
"""Run ruff."""
|
||||
_run(c, f"poetry run ruff check {PYTHON_TARGETS_STR}")
|
||||
_run(c, f"uv run ruff check {PYTHON_TARGETS_STR}")
|
||||
|
||||
|
||||
@task()
|
||||
def security(c: Context) -> None:
|
||||
"""Run security related checks."""
|
||||
# _run(
|
||||
# c,
|
||||
# "poetry export --with dev --format=requirements.txt --without-hashes | "
|
||||
# "poetry run safety check --stdin --full-report",
|
||||
# )
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
temp_file = f.name
|
||||
try:
|
||||
_run(c, f"uv export --extra dev --format requirements-txt --no-hashes > {temp_file}")
|
||||
_run(
|
||||
c,
|
||||
f"uv run pip-audit --requirement {temp_file} --format json",
|
||||
)
|
||||
finally:
|
||||
# Clean up
|
||||
if os.path.exists(temp_file):
|
||||
os.unlink(temp_file)
|
||||
return None
|
||||
|
||||
|
||||
@task(pre=[ruff, security, call(format_, check=True)])
|
||||
@task(pre=[ruff, call(format_, check=True)])
|
||||
def lint(c: Context) -> None:
|
||||
"""Run all linting."""
|
||||
|
||||
@@ -119,14 +128,14 @@ def lint(c: Context) -> None:
|
||||
@task()
|
||||
def mypy(c: Context) -> None:
|
||||
"""Run mypy."""
|
||||
_run(c, f"poetry run mypy {PYTHON_TARGETS_STR}")
|
||||
_run(c, f"uv run mypy {PYTHON_TARGETS_STR}")
|
||||
|
||||
|
||||
@task()
|
||||
def tests(c: Context) -> None:
|
||||
"""Run tests."""
|
||||
pytest_options = ["--xdoctest", "--cov", "--cov-report=", "--cov-fail-under=0"]
|
||||
_run(c, f"poetry run pytest {' '.join(pytest_options)} {TEST_DIR} {SOURCE_DIR}")
|
||||
_run(c, f"uv run pytest {' '.join(pytest_options)} {TEST_DIR} {SOURCE_DIR}")
|
||||
|
||||
|
||||
@task(
|
||||
@@ -138,8 +147,8 @@ def tests(c: Context) -> None:
|
||||
def coverage(c: Context, fmt: str = "report", open_browser: bool = False) -> None:
|
||||
"""Create coverage report."""
|
||||
if any(Path().glob(".coverage.*")):
|
||||
_run(c, "poetry run coverage combine")
|
||||
_run(c, f"poetry run coverage {fmt} -i")
|
||||
_run(c, "uv run coverage combine")
|
||||
_run(c, f"uv run coverage {fmt} -i")
|
||||
if fmt == "html" and open_browser:
|
||||
webbrowser.open(COVERAGE_REPORT.as_uri())
|
||||
|
||||
@@ -158,7 +167,7 @@ def docs(c: Context, serve: bool = False, open_browser: bool = False) -> None:
|
||||
if open_browser:
|
||||
webbrowser.open(DOCS_INDEX.absolute().as_uri())
|
||||
if serve:
|
||||
_run(c, f"poetry run watchmedo shell-command -p '*.rst;*.md' -c '{build_docs}' -R -D .")
|
||||
_run(c, f"uv run watchmedo shell-command -p '*.rst;*.md' -c '{build_docs}' -R -D .")
|
||||
|
||||
|
||||
@task(
|
||||
@@ -170,4 +179,4 @@ def docs(c: Context, serve: bool = False, open_browser: bool = False) -> None:
|
||||
def version(c: Context, part: str, dry_run: bool = False) -> None:
|
||||
"""Bump version."""
|
||||
bump_options = ["--dry-run"] if dry_run else []
|
||||
_run(c, f"poetry run bump2version {' '.join(bump_options)} {part}")
|
||||
_run(c, f"uv run bump2version {' '.join(bump_options)} {part}")
|
||||
|
||||
Reference in New Issue
Block a user