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:
Bo
2025-07-25 23:40:19 -05:00
committed by GitHub
parent 8103fa4ab2
commit 6b6408b02b
17 changed files with 2706 additions and 3112 deletions

View File

@@ -20,16 +20,15 @@ jobs:
- name: Install system deps - name: Install system deps
shell: bash shell: bash
run: | run: |
pip install poetry pip install uv
poetry config virtualenvs.in-project true uv sync --extra dev --extra linters
poetry install --no-root --only dev --only linters --sync
- name: Run autoupdate - name: Run autoupdate
run: poetry run pre-commit autoupdate run: uv run pre-commit autoupdate
continue-on-error: true continue-on-error: true
- name: Run pre-commit - 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 - uses: peter-evans/create-pull-request@v7.0.8
with: with:

View File

@@ -23,12 +23,11 @@ jobs:
- name: Install system deps - name: Install system deps
shell: bash shell: bash
run: | run: |
pip install poetry pip install uv
poetry config virtualenvs.in-project true
- name: Build package - name: Build package
run: | run: |
poetry build --ansi uv build
- name: Publish package on PyPI - name: Publish package on PyPI
uses: pypa/gh-action-pypi-publish@v1.12.4 uses: pypa/gh-action-pypi-publish@v1.12.4

View File

@@ -21,13 +21,12 @@ jobs:
- name: Install system deps - name: Install system deps
shell: bash shell: bash
run: | run: |
pip install poetry pip install uv
poetry config virtualenvs.in-project true uv sync --extra dev --extra linters
poetry install --no-root --only dev --only linters --sync
- name: Linting - name: Linting
shell: bash shell: bash
run: poetry run pre-commit run --all-files run: uv run pre-commit run --all-files
tests: tests:
needs: linting needs: linting
@@ -49,18 +48,17 @@ jobs:
- name: Install system deps - name: Install system deps
shell: bash shell: bash
run: | run: |
pip install nox-poetry==1.1.0 pip install nox
pip install poetry==1.8.5 pip install uv
poetry config virtualenvs.in-project true
- name: Run mypy with nox - name: Run mypy with nox
shell: bash shell: bash
run: nox --force-color -s mypy-${{ matrix.python-version }} run: nox --force-color -s mypy-${{ matrix.python-version }}
- name: Install Playwright Browsers - name: Install dependencies and Playwright Browsers
run: | run: |
pip install playwright uv sync --extra dev --extra test
playwright install --with-deps # Ensures browsers and dependencies are installed uv run playwright install --with-deps
- name: Run tests with nox - name: Run tests with nox
shell: bash shell: bash
@@ -93,9 +91,8 @@ jobs:
# - name: Install system deps # - name: Install system deps
# shell: bash # shell: bash
# run: | # run: |
# pip install nox-poetry==1.1.0 # pip install nox==1.1.0
# pip install poetry==1.8.5 # pip install uv==1.8.5
# poetry config virtualenvs.in-project true
# - name: Download coverage data # - name: Download coverage data
# uses: actions/download-artifact@v4.1.8 # uses: actions/download-artifact@v4.1.8

View File

@@ -35,8 +35,8 @@ repos:
rev: 5295f87c0e261da61a7b919fc754e3a77edd98a7 rev: 5295f87c0e261da61a7b919fc754e3a77edd98a7
hooks: hooks:
- id: validate-cff - id: validate-cff
- repo: https://github.com/python-poetry/poetry - repo: https://github.com/astral-sh/uv-pre-commit
rev: 1.8.3 # uv version.
rev: 0.7.19
hooks: hooks:
- id: poetry-check - id: uv-lock
- id: poetry-install

View File

@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.9.6]
- Fix searching across regions.
- Switch from `poetry` to `uv` for development.
## [0.9.5] ## [0.9.5]
- [issue 155](https://github.com/BoPeng/ai-marketplace-monitor/issues/155) Fix output of pushbullet - [issue 155](https://github.com/BoPeng/ai-marketplace-monitor/issues/155) Fix output of pushbullet

View File

@@ -13,10 +13,10 @@ We take our open source community seriously and hold ourselves and other contrib
### Requirements ### 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/). 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: To install the local development requirements inside a virtual environment run:
``` ```
$ poetry install $ uv sync --all-extras
$ poetry run inv install-hooks $ 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. 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 Then install the tool from source code with command
```sh ```sh
poetry install uv sync
``` ```
## Contributing ## Contributing

79
MIGRATION_TO_UV.md Normal file
View 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

View File

@@ -128,7 +128,7 @@ playwright install
### Set up a notification method (optional) ### 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/) - Sign up for [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/) or [Ntfy](https://ntfy.sh/)
- Install the app on your phone - Install the app on your phone

View File

@@ -3,19 +3,17 @@
import platform import platform
import nox import nox
from nox_poetry import Session, session from nox import Session
nox.options.sessions = ["tests", "mypy"] nox.options.sessions = ["tests", "mypy"]
python_versions = ["3.10", "3.11", "3.12"] python_versions = ["3.10", "3.11", "3.12"]
@session(python=python_versions) @nox.session(python=python_versions)
def tests(session: Session) -> None: def tests(session: Session) -> None:
"""Run the test suite.""" """Run the test suite."""
session.install(".") session.run("uv", "sync", "--extra", "test", external=True)
session.install( session.install("invoke")
"invoke", "pytest", "xdoctest", "coverage[toml]", "pytest-cov", "pytest-playwright"
)
try: try:
session.run( session.run(
"inv", "inv",
@@ -29,24 +27,26 @@ def tests(session: Session) -> None:
session.notify("coverage") session.notify("coverage")
@session(python=python_versions) @nox.session(python=python_versions)
def coverage(session: Session) -> None: def coverage(session: Session) -> None:
"""Produce the coverage report.""" """Produce the coverage report."""
args = session.posargs if session.posargs and len(session._runner.manifest) == 1 else [] 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.run("inv", "coverage", *args)
@session(python=python_versions) @nox.session(python=python_versions)
def mypy(session: Session) -> None: def mypy(session: Session) -> None:
"""Type-check using mypy.""" """Type-check using mypy."""
session.install(".") session.run("uv", "sync", "--extra", "typing", external=True)
session.install("invoke", "mypy") session.install("invoke")
session.run("inv", "mypy") session.run("inv", "mypy")
@session(python="3.12") @nox.session(python="3.12")
def security(session: Session) -> None: def security(session: Session) -> None:
"""Scan dependencies for insecure packages.""" """Scan dependencies for insecure packages."""
session.install("invoke", "safety") session.run("uv", "sync", "--extra", "security", external=True)
session.install("invoke")
session.run("inv", "security") session.run("inv", "security")

2963
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,10 @@
[tool.poetry] [project]
name = "ai-marketplace-monitor" name = "ai-marketplace-monitor"
version = "0.9.5" version = "0.9.5"
description = "An AI-based tool for monitoring facebook marketplace" 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" readme = "README.md"
homepage = "https://github.com/BoPeng/ai-marketplace-monitor" requires-python = ">=3.10"
repository = "https://github.com/BoPeng/ai-marketplace-monitor"
documentation = "https://ai-marketplace-monitor.readthedocs.io"
keywords = ["ai-marketplace-monitor"] keywords = ["ai-marketplace-monitor"]
classifiers = [ classifiers = [
"Development Status :: 2 - Pre-Alpha", "Development Status :: 2 - Pre-Alpha",
@@ -14,66 +12,72 @@ classifiers = [
"License :: OSI Approved :: GNU Affero General Public License v3", "License :: OSI Approved :: GNU Affero General Public License v3",
"Natural Language :: English", "Natural Language :: English",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "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" "Bug Tracker" = "https://github.com/BoPeng/ai-marketplace-monitor/issues"
[tool.poetry.scripts] [project.scripts]
ai-marketplace-monitor = 'ai_marketplace_monitor.cli:app' ai-marketplace-monitor = "ai_marketplace_monitor.cli:app"
[tool.poetry.dependencies] [project.optional-dependencies]
python = ">=3.10" dev = [
typer = { extras = ["all"], version = ">=0.15.1,<0.17.0" } "pre-commit>=4.0.1",
playwright = ">=1.41.0" "invoke>=2.2.0",
rich = ">=13.7.0" "bump2version>=1.0.1",
"pushbullet.py" = ">=0.12.0" "watchdog[watchmedo]>=6.0.0",
diskcache = ">=5.6.3" "pip-audit>=2.9.0",
watchdog = ">=4.0.0" ]
openai = ">=1.24.0" test = [
parsedatetime = ">=2.5" "pytest>=8.3.3",
humanize = ">=4.0.0" "xdoctest>=1.2.0",
schedule = ">=1.2.2" "coverage[toml]>=7.6.7",
inflect = ">=7.0.0" "pytest-cov>=6.0.0",
pynput = ">=1.7.0" "pytest-playwright>=0.7.0",
pillow = ">=10.0.0" ]
jinja2 = ">=3.0.0" linters = [
pyparsing = ">=3.1.0" "isort>=5.13.2,<7.0.0",
requests = ">=2.30.0" "black>=24.10,<26.0",
CurrencyConverter = ">=0.18.0" "ruff>=0.9.2,<0.13.0",
tomli = { version = "2.2.1", markers = "python_version < '3.11'" } ]
security = [
[tool.poetry.group.dev.dependencies] "safety>=3.2.11",
pre-commit = "^4.0.1" ]
invoke = "^2.2.0" typing = [
bump2version = "^1.0.1" "mypy>=1.13.0",
watchdog = { version = "^6.0.0", extras = ["watchmedo"] } ]
docs = [
[tool.poetry.group.test.dependencies] "sphinx>=8.1.3",
pytest = "^8.3.3" "recommonmark>=0.7.1",
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"
[tool.coverage.paths] [tool.coverage.paths]
source = ["src", "*/site-packages"] source = ["src", "*/site-packages"]
@@ -175,7 +179,7 @@ include_trailing_comma = true
force_grid_wrap = 0 force_grid_wrap = 0
use_parentheses = true use_parentheses = true
line_length = 99 line_length = 99
known_third_party = ["invoke", "nox", "nox_poetry"] known_third_party = ["invoke", "nox"]
[tool.black] [tool.black]
line-length = 99 line-length = 99
@@ -186,7 +190,7 @@ warn_return_any = false
warn_unused_configs = true warn_unused_configs = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = ["pytest.*", "invoke.*", "nox.*", "nox_poetry.*"] module = ["pytest.*", "invoke.*", "nox.*"]
allow_redefinition = false allow_redefinition = false
check_untyped_defs = true check_untyped_defs = true
ignore_errors = false ignore_errors = false
@@ -201,5 +205,5 @@ warn_unreachable = true
warn_no_return = true warn_no_return = true
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["hatchling"]
build-backend = "poetry.masonry.api" build-backend = "hatchling.build"

View File

@@ -271,7 +271,7 @@ class OpenAIBackend(AIBackend):
res: AIResponse | None = AIResponse.from_cache(listing, item_config, marketplace_config) res: AIResponse | None = AIResponse.from_cache(listing, item_config, marketplace_config)
if res is not None: if res is not None:
if self.logger: 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)}.""" 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 return res

View File

@@ -321,6 +321,7 @@ locale = "Spanish"
'Location is approximate' = 'La ubicación es aproximada' 'Location is approximate' = 'La ubicación es aproximada'
"About this vehicle" = 'Descripción del vendedor' "About this vehicle" = 'Descripción del vendedor'
"Seller's description" = 'Información sobre este vehículo' "Seller's description" = 'Información sobre este vehículo'
"Browse Marketplace" = 'Explorar Marketplace'
[translation.zh] [translation.zh]
locale = "Chinese" locale = "Chinese"
@@ -331,3 +332,15 @@ locale = "Chinese"
'Location is approximate' = '我们只提供大概位置' 'Location is approximate' = '我们只提供大概位置'
"About this vehicle" = "车辆信息" "About this vehicle" = "车辆信息"
"Seller's description" = "卖家描述" "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'

View File

@@ -473,30 +473,19 @@ class FacebookMarketplace(Marketplace):
f"""{hilight(item_config.name)} from {hilight(cname or city)}""" f"""{hilight(item_config.name)} from {hilight(cname or city)}"""
+ (f" with radius={radius}" if radius else " with default radius") + (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.goto_url(
self.page, self.translator, self.logger marketplace_url + "&".join([f"query={quote(search_phrase)}", *options])
).get_listings() )
time.sleep(5)
if found_listings: found_listings = FacebookSearchResultPage(
break self.page, self.translator, self.logger
if retries > 5: ).get_listings()
if self.logger: time.sleep(5)
self.logger.error( if self.logger:
f"""{hilight("[Search]", "fail")} Failed to get search results for {search_phrase}""" self.logger.error(
) f"""{hilight("[Search]", "fail")} Failed to get search results for {search_phrase} from {city}"""
break )
else:
retries += 1
if self.logger:
self.logger.debug(
f"""{hilight("[Search]", "info")} Retrying to get search results for {search_phrase}"""
)
counter.increment(CounterItem.SEARCH_PERFORMED, item_config.name) counter.increment(CounterItem.SEARCH_PERFORMED, item_config.name)
@@ -688,6 +677,19 @@ class FacebookSearchResultPage(WebPage):
return valid_listings return valid_listings
def get_listings(self: "FacebookSearchResultPage") -> List[Listing]: 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 # find the grid box
try: try:
valid_listings = ( valid_listings = (
@@ -1138,6 +1140,6 @@ def parse_listing(
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except Exception: except Exception:
# try next page layout # try next page ayout
continue continue
return None return None

View File

@@ -23,7 +23,7 @@ class RegionConfig(BaseConfig):
def handle_radius(self: "RegionConfig") -> None: def handle_radius(self: "RegionConfig") -> None:
if isinstance(self.radius, int): if isinstance(self.radius, int):
self.radius = [self.radius] self.radius = [self.radius] * len(self.search_city)
elif not self.radius: elif not self.radius:
self.radius = [500] * len(self.search_city) self.radius = [500] * len(self.search_city)
elif len(self.radius) != len(self.search_city): elif len(self.radius) != len(self.search_city):

View File

@@ -3,7 +3,9 @@
Execute 'invoke --list' for guidance on using Invoke Execute 'invoke --list' for guidance on using Invoke
""" """
import os
import platform import platform
import tempfile
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -76,42 +78,49 @@ def clean(c: Context) -> None:
@task() @task()
def install_hooks(c: Context) -> None: def install_hooks(c: Context) -> None:
"""Install pre-commit hooks.""" """Install pre-commit hooks."""
_run(c, "poetry run pre-commit install") _run(c, "uv run pre-commit install")
@task() @task()
def hooks(c: Context) -> None: def hooks(c: Context) -> None:
"""Run pre-commit hooks.""" """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"}) @task(name="format", help={"check": "Checks if source is formatted without applying changes"})
def format_(c: Context, check: bool = False) -> None: def format_(c: Context, check: bool = False) -> None:
"""Format code.""" """Format code."""
isort_options = ["--check-only", "--diff"] if check else [] 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"] 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() @task()
def ruff(c: Context) -> None: def ruff(c: Context) -> None:
"""Run ruff.""" """Run ruff."""
_run(c, f"poetry run ruff check {PYTHON_TARGETS_STR}") _run(c, f"uv run ruff check {PYTHON_TARGETS_STR}")
@task() @task()
def security(c: Context) -> None: def security(c: Context) -> None:
"""Run security related checks.""" """Run security related checks."""
# _run( with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
# c, temp_file = f.name
# "poetry export --with dev --format=requirements.txt --without-hashes | " try:
# "poetry run safety check --stdin --full-report", _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 return None
@task(pre=[ruff, security, call(format_, check=True)]) @task(pre=[ruff, call(format_, check=True)])
def lint(c: Context) -> None: def lint(c: Context) -> None:
"""Run all linting.""" """Run all linting."""
@@ -119,14 +128,14 @@ def lint(c: Context) -> None:
@task() @task()
def mypy(c: Context) -> None: def mypy(c: Context) -> None:
"""Run mypy.""" """Run mypy."""
_run(c, f"poetry run mypy {PYTHON_TARGETS_STR}") _run(c, f"uv run mypy {PYTHON_TARGETS_STR}")
@task() @task()
def tests(c: Context) -> None: def tests(c: Context) -> None:
"""Run tests.""" """Run tests."""
pytest_options = ["--xdoctest", "--cov", "--cov-report=", "--cov-fail-under=0"] 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( @task(
@@ -138,8 +147,8 @@ def tests(c: Context) -> None:
def coverage(c: Context, fmt: str = "report", open_browser: bool = False) -> None: def coverage(c: Context, fmt: str = "report", open_browser: bool = False) -> None:
"""Create coverage report.""" """Create coverage report."""
if any(Path().glob(".coverage.*")): if any(Path().glob(".coverage.*")):
_run(c, "poetry run coverage combine") _run(c, "uv run coverage combine")
_run(c, f"poetry run coverage {fmt} -i") _run(c, f"uv run coverage {fmt} -i")
if fmt == "html" and open_browser: if fmt == "html" and open_browser:
webbrowser.open(COVERAGE_REPORT.as_uri()) 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: if open_browser:
webbrowser.open(DOCS_INDEX.absolute().as_uri()) webbrowser.open(DOCS_INDEX.absolute().as_uri())
if serve: 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( @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: def version(c: Context, part: str, dry_run: bool = False) -> None:
"""Bump version.""" """Bump version."""
bump_options = ["--dry-run"] if dry_run else [] 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}")

2450
uv.lock generated Normal file
View File

File diff suppressed because it is too large Load Diff