Skip to content

drop python 3.8 and support 3.12 #313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
locations = "src", "tests"


@nox.session(python=["3.8", "3.9", "3.10", "3.11"])
@nox.session(python=["3.9", "3.10", "3.11", "3.12"])
def tests(session: nox.Session) -> None:
session.install(".[tests]")
session.run(
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ license = {text = "Apache-2.0"}
authors = [{ name = "Iterative", email = "[email protected]" }]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Development Status :: 4 - Beta",
]
requires-python = ">=3.8"
requires-python = ">=3.9"
dynamic = ["version"]
dependencies = [
"gitpython>3",
Expand Down
6 changes: 3 additions & 3 deletions src/scmrepo/asyn.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import asyncio
import os
import threading
from typing import Any, List, Optional
from typing import Any, Optional

from fsspec.asyn import ( # noqa: F401, pylint:disable=unused-import
_selector_policy,
Expand All @@ -11,9 +11,9 @@
)

# dedicated async IO thread
iothread: List[Optional[threading.Thread]] = [None]
iothread: list[Optional[threading.Thread]] = [None]
# global DVC event loop
default_loop: List[Optional[asyncio.AbstractEventLoop]] = [None]
default_loop: list[Optional[asyncio.AbstractEventLoop]] = [None]
lock = threading.Lock()


Expand Down
8 changes: 4 additions & 4 deletions src/scmrepo/fs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import errno
import os
import posixpath
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Dict, Optional, Tuple
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, Optional

from fsspec.callbacks import _DEFAULT_CALLBACK
from fsspec.spec import AbstractFileSystem
Expand Down Expand Up @@ -169,7 +169,7 @@ def relparts(self, path, start=None):
def as_posix(cls, path):
return path

def _get_key(self, path: str) -> Tuple[str, ...]:
def _get_key(self, path: str) -> tuple[str, ...]:
path = self.abspath(path)
if path == self.root_marker:
return ()
Expand All @@ -184,7 +184,7 @@ def _open(
mode: str = "rb",
block_size: Optional[int] = None,
autocommit: bool = True,
cache_options: Optional[Dict] = None,
cache_options: Optional[dict] = None,
raw: bool = False,
**kwargs: Any,
) -> BinaryIO:
Expand All @@ -204,7 +204,7 @@ def _open(
errno.EISDIR, os.strerror(errno.EISDIR), path
) from exc

def info(self, path: str, **kwargs: Any) -> Dict[str, Any]:
def info(self, path: str, **kwargs: Any) -> dict[str, Any]:
key = self._get_key(path)
try:
# NOTE: to avoid wasting time computing object size, trie.info
Expand Down
18 changes: 7 additions & 11 deletions src/scmrepo/git/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@
import re
import typing
from collections import OrderedDict
from collections.abc import Mapping
from collections.abc import Iterable, Mapping
from contextlib import contextmanager
from functools import partialmethod
from typing import (
TYPE_CHECKING,
Callable,
ClassVar,
Dict,
Iterable,
Optional,
Tuple,
Type,
Union,
)

Expand Down Expand Up @@ -44,14 +40,14 @@

logger = logging.getLogger(__name__)

BackendCls = Type[BaseGitBackend]
BackendCls = type[BaseGitBackend]


_LOW_PRIO_BACKENDS = ("gitpython",)


class GitBackends(Mapping):
DEFAULT: ClassVar[Dict[str, BackendCls]] = {
DEFAULT: ClassVar[dict[str, BackendCls]] = {
"dulwich": DulwichBackend,
"pygit2": Pygit2Backend,
"gitpython": GitPythonBackend,
Expand All @@ -72,7 +68,7 @@ def __init__(self, selected: Optional[Iterable[str]], *args, **kwargs) -> None:
selected = selected or list(self.DEFAULT)
self.backends = OrderedDict((key, self.DEFAULT[key]) for key in selected)

self.initialized: Dict[str, BaseGitBackend] = {}
self.initialized: dict[str, BaseGitBackend] = {}

self.args = args
self.kwargs = kwargs
Expand Down Expand Up @@ -169,7 +165,7 @@ def is_sha(cls, rev):
return rev and cls.RE_HEXSHA.search(rev)

@classmethod
def split_ref_pattern(cls, ref: str) -> Tuple[str, str]:
def split_ref_pattern(cls, ref: str) -> tuple[str, str]:
name = cls.BAD_REF_CHARS_RE.split(ref, maxsplit=1)[0]
return name, ref[len(name) :]

Expand Down Expand Up @@ -492,8 +488,8 @@ def describe(
base: Optional[str] = None,
match: Optional[str] = None,
exclude: Optional[str] = None,
) -> Dict[str, Optional[str]]:
results: Dict[str, Optional[str]] = {}
) -> dict[str, Optional[str]]:
results: dict[str, Optional[str]] = {}
remained_revs = set()
if base == "refs/heads":
current_rev = self.get_rev()
Expand Down
7 changes: 4 additions & 3 deletions src/scmrepo/git/backend/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
from abc import ABC, abstractmethod
from collections.abc import Iterable, Mapping
from enum import Enum
from typing import TYPE_CHECKING, Callable, Iterable, Mapping, Optional, Tuple, Union
from typing import TYPE_CHECKING, Callable, Optional, Union

from scmrepo.exceptions import SCMError
from scmrepo.git.objects import GitObject
Expand Down Expand Up @@ -281,7 +282,7 @@ def _stash_push(
ref: str,
message: Optional[str] = None,
include_untracked: bool = False,
) -> Tuple[Optional[str], bool]:
) -> tuple[Optional[str], bool]:
"""Push a commit onto the specified stash.

Returns a tuple of the form (rev, need_reset) where need_reset
Expand Down Expand Up @@ -347,7 +348,7 @@ def checkout_index(
@abstractmethod
def status(
self, ignored: bool = False, untracked_files: str = "all"
) -> Tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
"""Return tuple of (staged_files, unstaged_files, untracked_files).

staged_files will be a dict mapping status (add, delete, modify) to a
Expand Down
39 changes: 17 additions & 22 deletions src/scmrepo/git/backend/dulwich/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@
import os
import re
import stat
from collections.abc import Iterable, Iterator, Mapping
from contextlib import closing
from functools import partial
from io import BytesIO, StringIO
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Mapping,
Optional,
Tuple,
Union,
)

Expand Down Expand Up @@ -151,18 +146,18 @@ def encoding(self) -> str:
return self._config.encoding
return self._config.backends[0].encoding

def get(self, section: Tuple[str, ...], name: str) -> str:
def get(self, section: tuple[str, ...], name: str) -> str:
"""Return the specified setting as a string."""
return self._config.get(section, name).decode(self.encoding)

def get_bool(self, section: Tuple[str, ...], name: str) -> bool:
def get_bool(self, section: tuple[str, ...], name: str) -> bool:
"""Return the specified setting as a boolean."""
value = self._config.get_boolean(section, name)
if value is None:
raise ValueError("setting is not a valid boolean")
return value

def get_multivar(self, section: Tuple[str, ...], name: str) -> Iterator[str]:
def get_multivar(self, section: tuple[str, ...], name: str) -> Iterator[str]:
"""Iterate over string values in the specified multivar setting."""
for value in self._config.get_multivar(section, name):
yield value.decode(self.encoding)
Expand Down Expand Up @@ -199,17 +194,17 @@ def __init__( # pylint:disable=W0231
except NotGitRepository as exc:
raise SCMError(f"{root_dir} is not a git repository") from exc

self._submodules: Dict[str, str] = self._find_submodules()
self._submodules: dict[str, str] = self._find_submodules()
self._stashes: dict = {}

def _find_submodules(self) -> Dict[str, str]:
def _find_submodules(self) -> dict[str, str]:
"""Return dict mapping submodule names to submodule paths.

Submodule paths will be relative to Git repo root.
"""
from dulwich.config import ConfigFile, parse_submodules

submodules: Dict[str, str] = {}
submodules: dict[str, str] = {}
config_path = os.path.join(self.root_dir, ".gitmodules")
if os.path.isfile(config_path):
config = ConfigFile.from_path(config_path)
Expand Down Expand Up @@ -332,7 +327,7 @@ def add(
self.repo.stage(list(self.repo.open_index()))
return

files: List[bytes] = [
files: list[bytes] = [
os.fsencode(fpath) for fpath in self._expand_paths(paths, force=force)
]
if update:
Expand All @@ -348,7 +343,7 @@ def add(
else:
self.repo.stage(files)

def _expand_paths(self, paths: List[str], force: bool = False) -> Iterator[str]:
def _expand_paths(self, paths: list[str], force: bool = False) -> Iterator[str]:
for path in paths:
if not os.path.isabs(path) and self._submodules:
# NOTE: If path is inside a submodule, Dulwich expects the
Expand Down Expand Up @@ -459,7 +454,7 @@ def is_tracked(self, path: str) -> bool:
return any(p == rel or p.startswith(rel_dir) for p in self.repo.open_index())

def is_dirty(self, untracked_files: bool = False) -> bool:
kwargs: Dict[str, Any] = {} if untracked_files else {"untracked_files": "no"}
kwargs: dict[str, Any] = {} if untracked_files else {"untracked_files": "no"}
return any(self.status(**kwargs))

def active_branch(self) -> str:
Expand Down Expand Up @@ -707,9 +702,9 @@ def fetch_refspecs(
fetch_refs = []

def determine_wants(
remote_refs: Dict[bytes, bytes],
remote_refs: dict[bytes, bytes],
depth: Optional[int] = None, # pylint: disable=unused-argument
) -> List[bytes]:
) -> list[bytes]:
fetch_refs.extend(
parse_reftuples(
DictRefsContainer(remote_refs),
Expand Down Expand Up @@ -782,7 +777,7 @@ def _stash_push(
ref: str,
message: Optional[str] = None,
include_untracked: bool = False,
) -> Tuple[Optional[str], bool]:
) -> tuple[Optional[str], bool]:
from dulwich.repo import InvalidUserIdentity

from scmrepo.git import Stash
Expand Down Expand Up @@ -836,8 +831,8 @@ def _describe(
) -> Mapping[str, Optional[str]]:
if not base:
base = "refs/tags"
rev_mapping: Dict[str, Optional[str]] = {}
results: Dict[str, Optional[str]] = {}
rev_mapping: dict[str, Optional[str]] = {}
results: dict[str, Optional[str]] = {}
for ref in self.iter_refs(base=base):
if (match and not fnmatch.fnmatch(ref, match)) or (
exclude and fnmatch.fnmatch(ref, exclude)
Expand Down Expand Up @@ -877,7 +872,7 @@ def checkout_index(

def status(
self, ignored: bool = False, untracked_files: str = "all"
) -> Tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
) -> tuple[Mapping[str, Iterable[str]], Iterable[str], Iterable[str]]:
from dulwich.porcelain import Error
from dulwich.porcelain import status as git_status

Expand Down Expand Up @@ -978,7 +973,7 @@ def check_attr(
_IDENTITY_RE = re.compile(r"(?P<name>.+)\s+<(?P<email>.+)>")


def _parse_identity(identity: str) -> Tuple[str, str]:
def _parse_identity(identity: str) -> tuple[str, str]:
m = _IDENTITY_RE.match(identity)
if not m:
raise SCMError("Could not parse tagger identity '{identity}'")
Expand Down
16 changes: 6 additions & 10 deletions src/scmrepo/git/backend/dulwich/asyncssh_vendor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
"""asyncssh SSH vendor for Dulwich."""
import asyncio
import os
from collections.abc import Coroutine, Iterator, Sequence
from typing import (
TYPE_CHECKING,
Callable,
Coroutine,
Dict,
Iterator,
List,
Optional,
Sequence,
cast,
)

Expand Down Expand Up @@ -47,7 +43,7 @@ def __init__(self, stderr: "SSHReader", loop: asyncio.AbstractEventLoop) -> None
self.stderr = stderr
self.loop = loop

async def _readlines(self) -> List[bytes]:
async def _readlines(self) -> list[bytes]:
lines = []
while True:
line = await self.stderr.readline()
Expand Down Expand Up @@ -140,12 +136,12 @@ def _process_public_key_ok_gh(self, _pkttype, _pktid, packet):

class InteractiveSSHClient(SSHClient):
_conn: Optional["SSHClientConnection"] = None
_keys_to_try: Optional[List["FilePath"]] = None
_passphrases: Dict[str, str]
_keys_to_try: Optional[list["FilePath"]] = None
_passphrases: dict[str, str]

def __init__(self, *args, **kwargs):
super(*args, **kwargs)
_passphrases: Dict[str, str] = {}
_passphrases: dict[str, str] = {}

def connection_made(self, conn: "SSHClientConnection") -> None:
self._conn = conn
Expand Down Expand Up @@ -267,7 +263,7 @@ def __init__(self, **kwargs) -> None:
async def _run_command(
self,
host: str,
command: List[str],
command: list[str],
username: Optional[str] = None,
port: Optional[int] = None,
password: Optional[str] = None,
Expand Down
9 changes: 5 additions & 4 deletions src/scmrepo/git/backend/dulwich/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Dict, Iterator, List, Optional, Union
from collections.abc import Iterator
from typing import Optional, Union

from dulwich.client import HTTPUnauthorized, Urllib3HttpGitClient

Expand Down Expand Up @@ -26,10 +27,10 @@ def __init__(
def _http_request(
self,
url: str,
headers: Optional[Dict[str, str]] = None,
headers: Optional[dict[str, str]] = None,
data: Optional[Union[bytes, Iterator[bytes]]] = None,
):
cached_chunks: List[bytes] = []
cached_chunks: list[bytes] = []

def _cached_data() -> Iterator[bytes]:
assert data is not None
Expand Down Expand Up @@ -64,7 +65,7 @@ def _cached_data() -> Iterator[bytes]:
self._store_credentials.approve()
return result

def _get_auth(self) -> Dict[str, str]:
def _get_auth(self) -> dict[str, str]:
from urllib3.util import make_headers

try:
Expand Down
Loading