Skip to content
Merged
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 .github/workflows/automated-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
container: ghcr.io/astral-sh/uv:python${{ matrix.pythonversion }}-alpine
strategy:
matrix:
pythonversion: ["3.10", "3.11", "3.12", "3.13"]
pythonversion: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion bin/all_tests_containerised.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ for arg in "$@"; do
targets="$targets $arg"
;;
ALL)
targets="3.10 3.11 3.12 3.13"
targets="3.10 3.11 3.12 3.13 3.14"
;;
*)
echo "Usage: $0 { 3.10 | 3.11 | ... | ALL }" >&2
Expand Down
25 changes: 11 additions & 14 deletions homely/_asyncioutils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# NOTE: this file is python3-only because of asyncio
import asyncio
import sys


def _runasync(stdoutfilter, stderrfilter, cmd, **kwargs):
assert asyncio is not None

async def _runandfilter(loop, cmd, **kwargs):
async def _runandfilter(cmd, **kwargs):
loop = asyncio.get_event_loop()

def factory():
return FilteringProtocol(asyncio.streams._DEFAULT_LIMIT, loop)

Expand Down Expand Up @@ -45,17 +48,11 @@ def pipe_connection_lost(self, fd, exc):
out, err = await process.communicate()
return process.returncode, out, err

_exception = None

def handleexception(loop, context):
nonlocal _exception
if _exception is None:
_exception = context["exception"]
run_kwargs = {}
if sys.version_info >= (3, 13):
run_kwargs['loop_factory'] = asyncio.EventLoop

# FIXME: probably shouldn't be using the main loop here
loop = asyncio.get_event_loop()
loop.set_exception_handler(handleexception)
result = loop.run_until_complete(_runandfilter(loop, cmd, **kwargs))
if _exception:
raise _exception
return result
return asyncio.run(
_runandfilter(cmd, **kwargs),
**run_kwargs,
)
6 changes: 0 additions & 6 deletions homely/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ def autoupdate(**kwargs):
UpdateStatus.NEVER,
UpdateStatus.NOCONN)

oldcwd = os.getcwd()
import daemon # type: ignore
with daemon.DaemonContext(
# When this CLI is invoked by tests inside the testing container it's
Expand All @@ -318,11 +317,6 @@ def autoupdate(**kwargs):
from homely._ui import setstreams
setstreams(f, f)

# we need to chdir back to the old working directory or imports
# will be broken!
if sys.version_info[0] < 3:
os.chdir(oldcwd)

cfg = RepoListConfig()
run_update(list(cfg.find_all()),
pullfirst=True,
Expand Down
32 changes: 4 additions & 28 deletions homely/_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class note(object):

def __init__(self, message, dash=None):
super(note, self).__init__()
self._log(self._getstream(), message, dash=dash)
self._unicodelog(self._getstream(), message, dash=dash)

def _getstream(self):
return _OUTSTREAM
Expand All @@ -74,13 +74,6 @@ def _unicodelog(self, stream, message, dash=None):
except KeyError:
_NOTECOUNT[self.__class__.__name__] = 1

def _asciilog(self, stream, message, dash=None):
if isinstance(message, unicode): # noqa: F821
message = message.encode('utf-8')
return self._unicodelog(stream, message, dash)

_log = _asciilog if sys.version_info[0] < 3 else _unicodelog

def __enter__(self):
global _INDENT
_INDENT += 1
Expand Down Expand Up @@ -111,18 +104,7 @@ class dirty(warn):


def _writepidfile():
if sys.version_info[0] < 3:
# Note: py2 doesn't have a way to open a file in 'x' mode so we just
# have to accept that a race condition is possible, although unlikely.
if not os.path.exists(RUNFILE):
with open(RUNFILE, 'w') as f:
f.write(str(os.getpid()))
return True
with open(RUNFILE) as f:
warn("Update is already running (PID={})".format(f.read().strip()))
return False

# python3 allows us to create the pid file without race conditions
# Create the pid file without race conditions
try:
with open(RUNFILE, 'x') as f:
f.write(str(os.getpid()))
Expand Down Expand Up @@ -374,10 +356,8 @@ def yesno(name, prompt, default=None, recommended=None, noprompt=None):
if recommended is not None:
rec = "[recommended={}] ".format("Y" if recommended else "N")

input_ = raw_input if sys.version_info[0] < 3 else input # noqa: F821

while True:
answer = input_("{} {} {} : ".format(prompt, rec, options))
answer = input("{} {} {} : ".format(prompt, rec, options))
if answer == "" and default is not None:
retval = default
break
Expand Down Expand Up @@ -413,11 +393,7 @@ def setcurrentrepo(info):
def _write(path, content):
with open(path + ".new", 'w') as f:
f.write(content)
if sys.version_info[0] < 3:
# use the less-reliable os.rename() on python2
os.rename(path + ".new", path)
else:
os.replace(path + ".new", path)
os.replace(path + ".new", path)


_PREV_SECTION = []
Expand Down
29 changes: 7 additions & 22 deletions homely/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import re
import shutil
import subprocess
import sys
import tempfile
from datetime import timedelta
from functools import partial
from importlib.machinery import SourceFileLoader
from itertools import chain
from os.path import exists, join
from typing import Any, Optional, Union
Expand All @@ -16,28 +16,13 @@
from homely._errors import JsonError
from homely._vcs import Repo, fromdict

try:
# python3.3+
from importlib.machinery import SourceFileLoader

def _loadmodule(name, path):
return SourceFileLoader(name, path).load_module()
except ImportError:
# python2
import imp
def _loadmodule(name, path):
return SourceFileLoader(name, path).load_module()

def _loadmodule(name, path):
return imp.load_source(name, path)

if sys.version_info[0] < 3:
def opentext(path, mode, *args, **kwargs):
if 'r' in mode:
mode = "U" + mode
return open(path, mode, *args, **kwargs)
else:
# for python3, we open text files with universal newline support
opentext = partial(open, newline="")

# for python3, we open text files with universal newline support
opentext = partial(open, newline="")

ROOT = join(os.environ['HOME'], '.homely')
REPO_CONFIG_PATH = join(ROOT, 'repos.json')
Expand Down Expand Up @@ -511,14 +496,14 @@ def filereplacer(filepath):
NL = firstline[len(stripped):]
assert NL in ("\r", "\n", "\r\n"), "Bad NL %r" % NL
origlines = chain([stripped],
(l.rstrip('\r\n') for l in orig))
(line.rstrip('\r\n') for line in orig))
yield tmp, origlines, NL
else:
yield tmp, None, "\n"
except NoChangesNeeded:
if os.path.exists(tmpname):
os.unlink(tmpname)
except:
except Exception:
if os.path.exists(tmpname):
os.unlink(tmpname)
raise
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ requires-python = ">=3.10"
version = "0.20.3"

dependencies = [
"python-daemon==2.3.0",
"requests==2.25.1",
"click==7.1.2",
"python-daemon>=2.3.0",
"requests>=2.25.1",
"click>=7.1.2",
# tomli is required before python 3.11
"tomli>=2.3.0 ; python_full_version < '3.11'",
]
Expand Down
8 changes: 0 additions & 8 deletions test/unit/homely/_vcs/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,6 @@ def test_git(tmpdir):
clone1repo.pullchanges()
assert os.path.exists(os.path.join(clone1path, 'file.txt'))

try:
# FIXME: not sure why I have to close the main loop here when I didn't
# attach anything to it ... :-(
import asyncio
asyncio.get_event_loop().close()
except ImportError:
pass


def test_repo_object_recognises_git_repos():
from homely._vcs.git import Repo
Expand Down
29 changes: 11 additions & 18 deletions test/unit/homely/test_engine2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
import os
import sys

from homely._test import contents, gettmpfilepath

Expand Down Expand Up @@ -222,19 +221,16 @@ def test_lineinfile_usage(tmpdir):
assert contents(f1) == "\n\nAAA\n\n\n"

# make sure LineInFile() respects existing line endings
# NOTE: in python2 we always use Universal Newlines when reading the file,
# which tricks us into using "\n" when writing the file
if sys.version_info[0] > 2:
# - windows
contents(f1, "AAA\r\nBBB\r\n")
e = Engine(cfgpath)
e.run(LineInFile(f1, "CCC"))
assert contents(f1) == "AAA\r\nBBB\r\nCCC\r\n"
# - mac
contents(f1, "AAA\rBBB\r")
e = Engine(cfgpath)
e.run(LineInFile(f1, "BBB", WHERE_TOP))
assert contents(f1) == "BBB\rAAA\r"
# - windows
contents(f1, "AAA\r\nBBB\r\n")
e = Engine(cfgpath)
e.run(LineInFile(f1, "CCC"))
assert contents(f1) == "AAA\r\nBBB\r\nCCC\r\n"
# - mac
contents(f1, "AAA\rBBB\r")
e = Engine(cfgpath)
e.run(LineInFile(f1, "BBB", WHERE_TOP))
assert contents(f1) == "BBB\rAAA\r"

# make sure a file that starts empty is left empty after cleanup
contents(f1, "")
Expand Down Expand Up @@ -581,10 +577,7 @@ def test_writefile_usage(tmpdir):
homely._engine2._ENGINE = e
data = {"z": [3, 4, 5, True], "y": "Hello world", "x": None}
with writefile(f3) as f:
if sys.version_info[0] < 3:
f.write(json.dumps(data, ensure_ascii=False))
else:
f.write(json.dumps(data))
f.write(json.dumps(data))
e.cleanup(e.RAISE)
del e
assert os.path.exists(f3)
Expand Down
Loading