From 274b0b2ec8bf76cda16ef8c569cfd06637f03fb6 Mon Sep 17 00:00:00 2001 From: Wu Tingfeng Date: Tue, 14 Nov 2023 04:07:21 +0800 Subject: [PATCH] Fix path join on Windows (#314) * Switch to GitHub Actions * Support Python 3.12 --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++++++++++++ .travis.yml | 23 --------------------- README.md | 2 +- pyproject.toml | 1 + tests/custom_suffix_test.py | 10 +++++++--- tests/test_cache.py | 11 +++++----- tests/test_parallel.py | 21 ++++++++++++++++--- tldextract/cache.py | 16 +++++++-------- tox.ini | 2 +- 9 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..acb4d300 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: build +on: [push, pull_request] +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + language: + [ + {python-version: "3.8", toxenv: "py38"}, + {python-version: "3.9", toxenv: "py39"}, + {python-version: "3.10", toxenv: "py310"}, + {python-version: "3.11", toxenv: "py311"}, + {python-version: "3.12", toxenv: "py312"}, + {python-version: "pypy3.8", toxenv: "pypy38"}, + ] + include: + - os: ubuntu-latest + language: {python-version: "3.8", toxenv: "codestyle"} + - os: ubuntu-latest + language: {python-version: "3.8", toxenv: "lint"} + - os: ubuntu-latest + language: {python-version: "3.8", toxenv: "typecheck"} + runs-on: ${{ matrix.os }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.language.python-version }} + - name: Install Python requirements + run: | + pip install --upgrade pip + pip install --upgrade --editable '.[testing]' + - name: Test + run: tox + env: + TOXENV: ${{ matrix.language.toxenv }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ef2187cc..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -dist: focal -language: python -matrix: - include: - - python: "3.8" - env: TOXENV=py38 - - python: "3.9" - env: TOXENV=py39 - - python: "3.10" - env: TOXENV=py310 - - python: "3.11" - env: TOXENV=py311 - - python: pypy3.8-7.3.9 - dist: xenial - env: TOXENV=pypy3 - - env: TOXENV=codestyle - - env: TOXENV=lint - - env: TOXENV=typecheck -python: "3.10" -install: - - pip install --upgrade pip - - pip install --upgrade --editable '.[testing]' -script: tox diff --git a/README.md b/README.md index 9434d34b..c00e29fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# tldextract [![PyPI version](https://badge.fury.io/py/tldextract.svg)](https://badge.fury.io/py/tldextract) [![Build Status](https://travis-ci.com/john-kurkowski/tldextract.svg?branch=master)](https://app.travis-ci.com/github/john-kurkowski/tldextract) +# tldextract [![PyPI version](https://badge.fury.io/py/tldextract.svg)](https://badge.fury.io/py/tldextract) [![Build Status](https://github.com/john-kurkowski/tldextract/actions/workflows/ci.yml/badge.svg)](https://github.com/john-kurkowski/tldextract/actions/workflows/ci.yml) `tldextract` accurately separates a URL's subdomain, domain, and public suffix, using [the Public Suffix List (PSL)](https://publicsuffix.org). diff --git a/pyproject.toml b/pyproject.toml index d323aae7..07dcd9da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] requires-python = ">=3.8" dynamic = ["version"] diff --git a/tests/custom_suffix_test.py b/tests/custom_suffix_test.py index 1621d0e1..771531e6 100644 --- a/tests/custom_suffix_test.py +++ b/tests/custom_suffix_test.py @@ -2,13 +2,17 @@ import os import tempfile +from pathlib import Path import tldextract from tldextract.tldextract import ExtractResult -FAKE_SUFFIX_LIST_URL = "file://" + os.path.join( - os.path.dirname(os.path.abspath(__file__)), "fixtures/fake_suffix_list_fixture.dat" -) +FAKE_SUFFIX_LIST_URL = Path( + os.path.dirname(os.path.abspath(__file__)), + "fixtures", + "fake_suffix_list_fixture.dat", +).as_uri() + EXTRA_SUFFIXES = ["foo1", "bar1", "baz1"] extract_using_fake_suffix_list = tldextract.TLDExtract( diff --git a/tests/test_cache.py b/tests/test_cache.py index 981f7604..00bd5a39 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,7 +1,6 @@ """Test the caching functionality.""" from __future__ import annotations -import os.path import sys import types from collections.abc import Hashable @@ -56,14 +55,14 @@ def test_get_cache_dir(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("HOME", raising=False) monkeypatch.delenv("XDG_CACHE_HOME", raising=False) monkeypatch.delenv("TLDEXTRACT_CACHE", raising=False) - assert get_cache_dir().endswith("tldextract/.suffix_cache/") + assert get_cache_dir().endswith(str(Path("tldextract", ".suffix_cache"))) # with home set, but not anything else specified, use XDG_CACHE_HOME default monkeypatch.setenv("HOME", "/home/john") monkeypatch.delenv("XDG_CACHE_HOME", raising=False) monkeypatch.delenv("TLDEXTRACT_CACHE", raising=False) - assert get_cache_dir() == os.path.join( - "/home/john", ".cache/python-tldextract", pkg_identifier + assert get_cache_dir() == str( + Path("/home/john", ".cache/python-tldextract", pkg_identifier) ) # if XDG_CACHE_HOME is set, use it @@ -71,8 +70,8 @@ def test_get_cache_dir(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("XDG_CACHE_HOME", "/my/alt/cache") monkeypatch.delenv("TLDEXTRACT_CACHE", raising=False) - assert get_cache_dir() == os.path.join( - "/my/alt/cache/python-tldextract", pkg_identifier + assert get_cache_dir() == str( + Path("/my/alt/cache/python-tldextract", pkg_identifier) ) # if TLDEXTRACT_CACHE is set, use it diff --git a/tests/test_parallel.py b/tests/test_parallel.py index bb332296..6ae10dfd 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -1,7 +1,8 @@ """Test ability to run in parallel with shared cache.""" +from __future__ import annotations + import os -import os.path from multiprocessing import Pool from pathlib import Path @@ -43,9 +44,23 @@ def test_cache_cleared_by_other_process( extract("google.com") orig_unlink = os.unlink - def evil_unlink(filename: str) -> None: + def is_relative_to(path: Path, other_path: str | Path) -> bool: + """Return True if path is relative to other_path or False. + + Taken from the Python 3.9 standard library. + Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to + """ + try: + path.relative_to(other_path) + return True + except ValueError: + return False + + def evil_unlink(filename: str | Path) -> None: """Simulate someone deletes the file right before we try to.""" - if filename.startswith(cache_dir): + if (isinstance(filename, str) and filename.startswith(cache_dir)) or ( + isinstance(filename, Path) and is_relative_to(filename, cache_dir) + ): orig_unlink(filename) orig_unlink(filename) diff --git a/tldextract/cache.py b/tldextract/cache.py index d33bc8aa..d72eaac0 100644 --- a/tldextract/cache.py +++ b/tldextract/cache.py @@ -6,9 +6,9 @@ import json import logging import os -import os.path import sys from collections.abc import Callable, Hashable, Iterable +from pathlib import Path from typing import ( TypeVar, cast, @@ -79,15 +79,15 @@ def get_cache_dir() -> str: if xdg_cache_home is None: user_home = os.getenv("HOME", None) if user_home: - xdg_cache_home = os.path.join(user_home, ".cache") + xdg_cache_home = str(Path(user_home, ".cache")) if xdg_cache_home is not None: - return os.path.join( - xdg_cache_home, "python-tldextract", get_pkg_unique_identifier() + return str( + Path(xdg_cache_home, "python-tldextract", get_pkg_unique_identifier()) ) # fallback to trying to use package directory itself - return os.path.join(os.path.dirname(__file__), ".suffix_cache/") + return str(Path(os.path.dirname(__file__), ".suffix_cache")) class DiskCache: @@ -153,7 +153,7 @@ def clear(self) -> None: self.file_ext + ".lock" ): try: - os.unlink(os.path.join(root, filename)) + os.unlink(str(Path(root, filename))) except FileNotFoundError: pass except OSError as exc: @@ -165,10 +165,10 @@ def clear(self) -> None: def _key_to_cachefile_path( self, namespace: str, key: str | dict[str, Hashable] ) -> str: - namespace_path = os.path.join(self.cache_dir, namespace) + namespace_path = str(Path(self.cache_dir, namespace)) hashed_key = _make_cache_key(key) - cache_path = os.path.join(namespace_path, hashed_key + self.file_ext) + cache_path = str(Path(namespace_path, hashed_key + self.file_ext)) return cache_path diff --git a/tox.ini b/tox.ini index 89b8802c..0e4ea2e7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{38,39,310,311,py3},codestyle,lint,typecheck +envlist = py{38,39,310,311,312,py38},codestyle,lint,typecheck [testenv] commands = pytest {posargs}