From 93dff0e6adbe718a15e3479d0de21e4a978ac9d7 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 9 Mar 2021 15:42:48 -0500 Subject: [PATCH 01/46] Use `sphinx_rtd_theme` when building docs locally Default theme that Sphinx uses is not consistent with what we have at https://tahoe-lafs.readthedocs.io. Being consistent with rtd might help future doc writers. --- docs/conf.py | 4 ++-- newsfragments/3631.minor | 0 tox.ini | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 newsfragments/3631.minor diff --git a/docs/conf.py b/docs/conf.py index 612c324a3..af05e5900 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ import os # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['recommonmark'] +extensions = ['recommonmark', 'sphinx_rtd_theme'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -107,7 +107,7 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/newsfragments/3631.minor b/newsfragments/3631.minor new file mode 100644 index 000000000..e69de29bb diff --git a/tox.ini b/tox.ini index 33e5830ff..8908142f4 100644 --- a/tox.ini +++ b/tox.ini @@ -230,6 +230,7 @@ deps = sphinx docutils==0.12 recommonmark + sphinx_rtd_theme # normal install is not needed for docs, and slows things down skip_install = True commands = From 61fc96181ee0a154eba1906ddefd033eefd3ff63 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 9 Mar 2021 16:57:20 -0500 Subject: [PATCH 02/46] Build docs on CI --- .circleci/config.yml | 12 ++++++++++++ newsfragments/3632.minor | 0 2 files changed, 12 insertions(+) create mode 100644 newsfragments/3632.minor diff --git a/.circleci/config.yml b/.circleci/config.yml index b00bcdcec..2daf3b93f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -458,6 +458,18 @@ jobs: command: | /tmp/venv/bin/tox -e typechecks + docs: + docker: + - <<: *DOCKERHUB_AUTH + image: "tahoelafsci/ubuntu:18.04-py3" + + steps: + - "checkout" + - run: + name: "Build documentation" + command: | + /tmp/venv/bin/tox -e docs + build-image: &BUILD_IMAGE # This is a template for a job to build a Docker image that has as much of # the setup as we can manage already done and baked in. This cuts down on diff --git a/newsfragments/3632.minor b/newsfragments/3632.minor new file mode 100644 index 000000000..e69de29bb From 425313457b24fb663cebe6394fd71afb9b1d4b32 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:41:56 -0400 Subject: [PATCH 03/46] Make sure we're not doing `"%s" % (bytes_)`. --- src/allmydata/scripts/tahoe_add_alias.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/allmydata/scripts/tahoe_add_alias.py b/src/allmydata/scripts/tahoe_add_alias.py index 6f931556d..74dd7597f 100644 --- a/src/allmydata/scripts/tahoe_add_alias.py +++ b/src/allmydata/scripts/tahoe_add_alias.py @@ -1,6 +1,8 @@ from __future__ import print_function from __future__ import unicode_literals +from past.builtins import unicode + import os.path import codecs import json @@ -52,7 +54,7 @@ def add_alias(options): show_output(stderr, "Alias {alias} already exists!", alias=alias) return 1 aliasfile = os.path.join(nodedir, "private", "aliases") - cap = uri.from_string_dirnode(cap).to_string() + cap = unicode(uri.from_string_dirnode(cap).to_string(), 'utf-8') add_line_to_aliasfile(aliasfile, alias, cap) show_output(stdout, "Alias {alias} added", alias=alias) @@ -92,7 +94,7 @@ def create_alias(options): # probably check for others.. - add_line_to_aliasfile(aliasfile, alias, new_uri) + add_line_to_aliasfile(aliasfile, alias, unicode(new_uri, "utf-8")) show_output(stdout, "Alias {alias} created", alias=alias) return 0 From 1c59d349bacdcd78408c07b0a52675ddbe45ca8f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:42:18 -0400 Subject: [PATCH 04/46] It was always bytes. --- src/allmydata/test/cli/test_alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/test/cli/test_alias.py b/src/allmydata/test/cli/test_alias.py index 07f42b29d..c1ec96910 100644 --- a/src/allmydata/test/cli/test_alias.py +++ b/src/allmydata/test/cli/test_alias.py @@ -59,7 +59,7 @@ class ListAlias(GridTestMixin, CLITestMixin, unittest.TestCase): # the node filesystem state. aliases = get_aliases(self.get_clientdir()) self.assertIn(alias, aliases) - self.assertTrue(aliases[alias].startswith(u"URI:DIR2:")) + self.assertTrue(aliases[alias].startswith(b"URI:DIR2:")) # And inspect the state via the user interface list-aliases command # too. From f1476690715a1c8a084d42324b569c76c3d6365b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:44:36 -0400 Subject: [PATCH 05/46] Tests pass on Python 3. --- src/allmydata/scripts/tahoe_add_alias.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/allmydata/scripts/tahoe_add_alias.py b/src/allmydata/scripts/tahoe_add_alias.py index 74dd7597f..5faa0dae1 100644 --- a/src/allmydata/scripts/tahoe_add_alias.py +++ b/src/allmydata/scripts/tahoe_add_alias.py @@ -5,7 +5,6 @@ from past.builtins import unicode import os.path import codecs -import json from allmydata.util.assertutil import precondition @@ -14,6 +13,7 @@ from allmydata.scripts.common_http import do_http, check_http_error from allmydata.scripts.common import get_aliases from allmydata.util.fileutil import move_into_place from allmydata.util.encodingutil import quote_output, quote_output_u +from allmydata.util import jsonbytes as json def add_line_to_aliasfile(aliasfile, alias, cap): @@ -169,7 +169,10 @@ def list_aliases(options): data = _get_alias_details(options['node-directory']) if options['json']: - output = _escape_format(json.dumps(data, indent=4).decode("ascii")) + dumped = json.dumps(data, indent=4) + if isinstance(dumped, bytes): + loaded = dumped.decode("utf-8") + output = _escape_format(dumped) else: def dircap(details): return ( From 876235382e487776e06da3ec3abfb912dbbb16bd Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:50:28 -0400 Subject: [PATCH 06/46] Port to Python 3. --- src/allmydata/test/cli/test_alias.py | 12 ++++++++++++ src/allmydata/util/_python3.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/allmydata/test/cli/test_alias.py b/src/allmydata/test/cli/test_alias.py index c1ec96910..a3ee595b8 100644 --- a/src/allmydata/test/cli/test_alias.py +++ b/src/allmydata/test/cli/test_alias.py @@ -1,3 +1,15 @@ +""" +Ported to Python 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + import json from twisted.trial import unittest diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index e5e616674..050a3b84d 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -136,6 +136,8 @@ PORTED_MODULES = [ ] PORTED_TEST_MODULES = [ + "allmydata.test.cli.test_alias", + "allmydata.test.mutable.test_checker", "allmydata.test.mutable.test_datahandle", "allmydata.test.mutable.test_different_encoding", From a6c0031d74a701df294d4b521067244343aeec9d Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:50:59 -0400 Subject: [PATCH 07/46] News file. --- newsfragments/3634.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3634.minor diff --git a/newsfragments/3634.minor b/newsfragments/3634.minor new file mode 100644 index 000000000..e69de29bb From 1fde7fc007af02daedec916611db2e1d41fc02f8 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 09:58:31 -0400 Subject: [PATCH 08/46] Fix import. --- src/allmydata/test/cli/test_backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/cli/test_backup.py b/src/allmydata/test/cli/test_backup.py index ceecbd662..0532d1208 100644 --- a/src/allmydata/test/cli/test_backup.py +++ b/src/allmydata/test/cli/test_backup.py @@ -2,11 +2,11 @@ import os.path from six.moves import cStringIO as StringIO from datetime import timedelta import re +import builtins from twisted.trial import unittest from twisted.python.monkey import MonkeyPatcher -import __builtin__ from allmydata.util import fileutil from allmydata.util.fileutil import abspath_expanduser_unicode from allmydata.util.encodingutil import get_io_encoding, unicode_to_argv @@ -412,7 +412,7 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): self.failUnlessEqual(name, abspath_expanduser_unicode(exclude_file)) return StringIO() - patcher = MonkeyPatcher((__builtin__, 'file', call_file)) + patcher = MonkeyPatcher((builtins, 'file', call_file)) patcher.runWithPatches(parse_options, basedir, "backup", ['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to']) self.failUnless(ns.called) From 44374487c7ef5348372e3d694a3c9d2c955af62d Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 15 Mar 2021 10:37:03 -0400 Subject: [PATCH 09/46] Some progress towards passing tests on Python 3. --- src/allmydata/scripts/cli.py | 2 +- src/allmydata/scripts/common.py | 10 +++++----- src/allmydata/scripts/tahoe_backup.py | 10 +++++----- src/allmydata/test/cli/common.py | 2 +- src/allmydata/test/cli/test_backup.py | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 6c5641b41..811ae7ef9 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -351,7 +351,7 @@ class BackupOptions(FileStoreOptions): line. The file is assumed to be in the argv encoding.""" abs_filepath = argv_to_abspath(filepath) try: - exclude_file = file(abs_filepath) + exclude_file = open(abs_filepath) except: raise BackupConfigurationError('Error opening exclude file %s.' % quote_local_unicode_path(abs_filepath)) try: diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index bcaa84649..750b86718 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -228,19 +228,19 @@ def get_alias(aliases, path_unicode, default): precondition(isinstance(path_unicode, str), path_unicode) from allmydata import uri - path = path_unicode.encode('utf-8').strip(" ") + path = path_unicode.encode('utf-8').strip(b" ") if uri.has_uri_prefix(path): # We used to require "URI:blah:./foo" in order to get a subpath, # stripping out the ":./" sequence. We still allow that for compatibility, # but now also allow just "URI:blah/foo". - sep = path.find(":./") + sep = path.find(b":./") if sep != -1: return path[:sep], path[sep+3:] - sep = path.find("/") + sep = path.find(b"/") if sep != -1: return path[:sep], path[sep+1:] - return path, "" - colon = path.find(":") + return path, b"" + colon = path.find(b":") if colon == -1: # no alias if default == None: diff --git a/src/allmydata/scripts/tahoe_backup.py b/src/allmydata/scripts/tahoe_backup.py index c63558eb1..99f5663ea 100644 --- a/src/allmydata/scripts/tahoe_backup.py +++ b/src/allmydata/scripts/tahoe_backup.py @@ -2,7 +2,7 @@ from __future__ import print_function import os.path import time -import urllib +from urllib.parse import quote as url_quote import json import datetime from allmydata.scripts.common import get_alias, escape_path, DEFAULT_ALIAS, \ @@ -52,7 +52,7 @@ def mkdir(contents, options): def put_child(dirurl, childname, childcap): assert dirurl[-1] != "/" - url = dirurl + "/" + urllib.quote(unicode_to_url(childname)) + "?t=uri" + url = dirurl + "/" + url_quote(unicode_to_url(childname)) + "?t=uri" resp = do_http("PUT", url, childcap) if resp.status not in (200, 201): raise HTTPError("Error during put_child", resp) @@ -97,7 +97,7 @@ class BackerUpper(object): except UnknownAliasError as e: e.display(stderr) return 1 - to_url = nodeurl + "uri/%s/" % urllib.quote(rootcap) + to_url = nodeurl + "uri/%s/" % url_quote(rootcap) if path: to_url += escape_path(path) if not to_url.endswith("/"): @@ -192,7 +192,7 @@ class BackerUpper(object): filecap = r.was_uploaded() self.verboseprint("checking %s" % quote_output(filecap)) nodeurl = self.options['node-url'] - checkurl = nodeurl + "uri/%s?t=check&output=JSON" % urllib.quote(filecap) + checkurl = nodeurl + "uri/%s?t=check&output=JSON" % url_quote(filecap) self._files_checked += 1 resp = do_http("POST", checkurl) if resp.status != 200: @@ -225,7 +225,7 @@ class BackerUpper(object): dircap = r.was_created() self.verboseprint("checking %s" % quote_output(dircap)) nodeurl = self.options['node-url'] - checkurl = nodeurl + "uri/%s?t=check&output=JSON" % urllib.quote(dircap) + checkurl = nodeurl + "uri/%s?t=check&output=JSON" % url_quote(dircap) self._directories_checked += 1 resp = do_http("POST", checkurl) if resp.status != 200: diff --git a/src/allmydata/test/cli/common.py b/src/allmydata/test/cli/common.py index 3033aed4b..8796f815f 100644 --- a/src/allmydata/test/cli/common.py +++ b/src/allmydata/test/cli/common.py @@ -55,5 +55,5 @@ class CLITestMixin(ReallyEqualMixin): verb = ensure_str(verb) args = [ensure_str(arg) for arg in args] client_dir = ensure_str(self.get_clientdir(i=client_num)) - nodeargs = [ b"--node-directory", client_dir ] + nodeargs = [ "--node-directory", client_dir ] return run_cli(verb, *args, nodeargs=nodeargs, **kwargs) diff --git a/src/allmydata/test/cli/test_backup.py b/src/allmydata/test/cli/test_backup.py index 0532d1208..8e2d09544 100644 --- a/src/allmydata/test/cli/test_backup.py +++ b/src/allmydata/test/cli/test_backup.py @@ -412,7 +412,7 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): self.failUnlessEqual(name, abspath_expanduser_unicode(exclude_file)) return StringIO() - patcher = MonkeyPatcher((builtins, 'file', call_file)) + patcher = MonkeyPatcher((builtins, 'open', call_file)) patcher.runWithPatches(parse_options, basedir, "backup", ['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to']) self.failUnless(ns.called) From 91873d5a15c403bfbcbc824815d6ec71d9ee605b Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 17 Mar 2021 14:45:10 -0400 Subject: [PATCH 10/46] news fragment --- newsfragments/3638.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3638.minor diff --git a/newsfragments/3638.minor b/newsfragments/3638.minor new file mode 100644 index 000000000..e69de29bb From 6955154824037687a146286d01ceebe6a95bf402 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 17 Mar 2021 15:17:19 -0400 Subject: [PATCH 11/46] Allow SubCommands to be defined with any Options --- src/allmydata/scripts/types_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/scripts/types_.py b/src/allmydata/scripts/types_.py index 3937cb803..77fe4b381 100644 --- a/src/allmydata/scripts/types_.py +++ b/src/allmydata/scripts/types_.py @@ -1,11 +1,11 @@ from typing import List, Tuple, Type, Sequence, Any -from allmydata.scripts.common import BaseOptions +from twisted.python.usage import Options # Historically, subcommands were implemented as lists, but due to a # [designed contraint in mypy](https://stackoverflow.com/a/52559625/70170), # a Tuple is required. -SubCommand = Tuple[str, None, Type[BaseOptions], str] +SubCommand = Tuple[str, None, Type[Options], str] SubCommands = List[SubCommand] From 84cc7c5b848cbc8993c62bb5e27353b5fd14accb Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 17 Mar 2021 15:17:57 -0400 Subject: [PATCH 12/46] Tell mypy about the types of optFlags and optParameters --- src/allmydata/scripts/create_node.py | 18 +++++++++++------- src/allmydata/scripts/types_.py | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py index 0f507f518..ce031dd6b 100644 --- a/src/allmydata/scripts/create_node.py +++ b/src/allmydata/scripts/create_node.py @@ -4,7 +4,11 @@ import os import json try: - from allmydata.scripts.types_ import SubCommands + from allmydata.scripts.types_ import ( + SubCommands, + Parameters, + Flags, + ) except ImportError: pass @@ -47,29 +51,29 @@ WHERE_OPTS = [ "Hostname to automatically set --location/--port when --listen=tcp"), ("listen", None, "tcp", "Comma-separated list of listener types (tcp,tor,i2p,none)."), -] +] # type: Parameters TOR_OPTS = [ ("tor-control-port", None, None, "Tor's control port endpoint descriptor string (e.g. tcp:127.0.0.1:9051 or unix:/var/run/tor/control)"), ("tor-executable", None, None, "The 'tor' executable to run (default is to search $PATH)."), -] +] # type: Parameters TOR_FLAGS = [ ("tor-launch", None, "Launch a tor instead of connecting to a tor control port."), -] +] # type: Flags I2P_OPTS = [ ("i2p-sam-port", None, None, "I2P's SAM API port endpoint descriptor string (e.g. tcp:127.0.0.1:7656)"), ("i2p-executable", None, None, "(future) The 'i2prouter' executable to run (default is to search $PATH)."), -] +] # type: Parameters I2P_FLAGS = [ ("i2p-launch", None, "(future) Launch an I2P router instead of connecting to a SAM API port."), -] +] # type: Flags def validate_where_options(o): if o['listen'] == "none": @@ -175,7 +179,7 @@ class CreateClientOptions(_CreateBaseOptions): ("shares-happy", None, 7, "How many servers new files must be placed on."), ("shares-total", None, 10, "Total shares required for uploaded files."), ("join", None, None, "Join a grid with the given Invite Code."), - ] + ] # type: Parameters # This is overridden in order to ensure we get a "Wrong number of # arguments." error when more than one argument is given. diff --git a/src/allmydata/scripts/types_.py b/src/allmydata/scripts/types_.py index 77fe4b381..4f866a496 100644 --- a/src/allmydata/scripts/types_.py +++ b/src/allmydata/scripts/types_.py @@ -10,3 +10,5 @@ SubCommand = Tuple[str, None, Type[Options], str] SubCommands = List[SubCommand] Parameters = List[Sequence[Any]] + +Flags = List[Tuple[str, None, str]] From b879314194e5463c3a592b5142bc547c0fbd815d Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 17 Mar 2021 15:21:22 -0400 Subject: [PATCH 13/46] Make these byte strings explicitly byte strings --- src/allmydata/scripts/tahoe_check.py | 4 ++-- src/allmydata/scripts/tahoe_manifest.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/scripts/tahoe_check.py b/src/allmydata/scripts/tahoe_check.py index 997ee6e9e..cef9e32be 100644 --- a/src/allmydata/scripts/tahoe_check.py +++ b/src/allmydata/scripts/tahoe_check.py @@ -122,7 +122,7 @@ class FakeTransport(object): disconnecting = False class DeepCheckOutput(LineOnlyReceiver, object): - delimiter = "\n" + delimiter = b"\n" def __init__(self, streamer, options): self.streamer = streamer self.transport = FakeTransport() @@ -181,7 +181,7 @@ class DeepCheckOutput(LineOnlyReceiver, object): % (self.num_objects, self.files_healthy, self.files_unhealthy), file=stdout) class DeepCheckAndRepairOutput(LineOnlyReceiver, object): - delimiter = "\n" + delimiter = b"\n" def __init__(self, streamer, options): self.streamer = streamer self.transport = FakeTransport() diff --git a/src/allmydata/scripts/tahoe_manifest.py b/src/allmydata/scripts/tahoe_manifest.py index 032c65d51..386cdd1ad 100644 --- a/src/allmydata/scripts/tahoe_manifest.py +++ b/src/allmydata/scripts/tahoe_manifest.py @@ -13,7 +13,7 @@ class FakeTransport(object): disconnecting = False class ManifestStreamer(LineOnlyReceiver, object): - delimiter = "\n" + delimiter = b"\n" def __init__(self): self.transport = FakeTransport() From 34f1f43e95e165608ecc5a5453456f507271afc9 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 10:31:58 -0400 Subject: [PATCH 14/46] Revert changes to tahoe_backup/test_backup for now. --- src/allmydata/scripts/cli.py | 2 +- src/allmydata/scripts/tahoe_backup.py | 10 +++++----- src/allmydata/test/cli/test_backup.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 811ae7ef9..6c5641b41 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -351,7 +351,7 @@ class BackupOptions(FileStoreOptions): line. The file is assumed to be in the argv encoding.""" abs_filepath = argv_to_abspath(filepath) try: - exclude_file = open(abs_filepath) + exclude_file = file(abs_filepath) except: raise BackupConfigurationError('Error opening exclude file %s.' % quote_local_unicode_path(abs_filepath)) try: diff --git a/src/allmydata/scripts/tahoe_backup.py b/src/allmydata/scripts/tahoe_backup.py index 99f5663ea..c63558eb1 100644 --- a/src/allmydata/scripts/tahoe_backup.py +++ b/src/allmydata/scripts/tahoe_backup.py @@ -2,7 +2,7 @@ from __future__ import print_function import os.path import time -from urllib.parse import quote as url_quote +import urllib import json import datetime from allmydata.scripts.common import get_alias, escape_path, DEFAULT_ALIAS, \ @@ -52,7 +52,7 @@ def mkdir(contents, options): def put_child(dirurl, childname, childcap): assert dirurl[-1] != "/" - url = dirurl + "/" + url_quote(unicode_to_url(childname)) + "?t=uri" + url = dirurl + "/" + urllib.quote(unicode_to_url(childname)) + "?t=uri" resp = do_http("PUT", url, childcap) if resp.status not in (200, 201): raise HTTPError("Error during put_child", resp) @@ -97,7 +97,7 @@ class BackerUpper(object): except UnknownAliasError as e: e.display(stderr) return 1 - to_url = nodeurl + "uri/%s/" % url_quote(rootcap) + to_url = nodeurl + "uri/%s/" % urllib.quote(rootcap) if path: to_url += escape_path(path) if not to_url.endswith("/"): @@ -192,7 +192,7 @@ class BackerUpper(object): filecap = r.was_uploaded() self.verboseprint("checking %s" % quote_output(filecap)) nodeurl = self.options['node-url'] - checkurl = nodeurl + "uri/%s?t=check&output=JSON" % url_quote(filecap) + checkurl = nodeurl + "uri/%s?t=check&output=JSON" % urllib.quote(filecap) self._files_checked += 1 resp = do_http("POST", checkurl) if resp.status != 200: @@ -225,7 +225,7 @@ class BackerUpper(object): dircap = r.was_created() self.verboseprint("checking %s" % quote_output(dircap)) nodeurl = self.options['node-url'] - checkurl = nodeurl + "uri/%s?t=check&output=JSON" % url_quote(dircap) + checkurl = nodeurl + "uri/%s?t=check&output=JSON" % urllib.quote(dircap) self._directories_checked += 1 resp = do_http("POST", checkurl) if resp.status != 200: diff --git a/src/allmydata/test/cli/test_backup.py b/src/allmydata/test/cli/test_backup.py index 8e2d09544..ceecbd662 100644 --- a/src/allmydata/test/cli/test_backup.py +++ b/src/allmydata/test/cli/test_backup.py @@ -2,11 +2,11 @@ import os.path from six.moves import cStringIO as StringIO from datetime import timedelta import re -import builtins from twisted.trial import unittest from twisted.python.monkey import MonkeyPatcher +import __builtin__ from allmydata.util import fileutil from allmydata.util.fileutil import abspath_expanduser_unicode from allmydata.util.encodingutil import get_io_encoding, unicode_to_argv @@ -412,7 +412,7 @@ class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): self.failUnlessEqual(name, abspath_expanduser_unicode(exclude_file)) return StringIO() - patcher = MonkeyPatcher((builtins, 'open', call_file)) + patcher = MonkeyPatcher((__builtin__, 'file', call_file)) patcher.runWithPatches(parse_options, basedir, "backup", ['--exclude-from', unicode_to_argv(exclude_file), 'from', 'to']) self.failUnless(ns.called) From b7d6b97f631ca857793d687cf02e89dae50a4c2b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 10:42:15 -0400 Subject: [PATCH 15/46] Some progress towards passing tests on Python 3. --- src/allmydata/scripts/tahoe_status.py | 12 +++++++----- src/allmydata/test/cli/test_status.py | 4 +--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/allmydata/scripts/tahoe_status.py b/src/allmydata/scripts/tahoe_status.py index 405a8c730..36c2d5daa 100644 --- a/src/allmydata/scripts/tahoe_status.py +++ b/src/allmydata/scripts/tahoe_status.py @@ -1,7 +1,9 @@ from __future__ import print_function +from future.builtins import chr + import os -import urllib +from urllib.parse import urlencode, quote as url_quote import json @@ -25,7 +27,7 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None): if method == 'POST': if post_args is None: raise ValueError("Must pass post_args= for POST method") - body = urllib.urlencode(post_args) + body = urlencode(post_args) else: body = '' if post_args is not None: @@ -48,7 +50,7 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None): def _get_json_for_cap(options, cap): return _get_json_for_fragment( options, - 'uri/%s?t=json' % urllib.quote(cap), + 'uri/%s?t=json' % url_quote(cap), ) def pretty_progress(percent, size=10, ascii=False): @@ -74,8 +76,8 @@ def pretty_progress(percent, size=10, ascii=False): # unicode 0x2581 -> 2589 are vertical bar chunks, like rainbarf uses # and following are narrow -> wider bars - part = unichr(0x258f - part) # for smooth bar - # part = unichr(0x2581 + part) # for neater-looking thing + part = chr(0x258f - part) # for smooth bar + # part = chr(0x2581 + part) # for neater-looking thing # hack for 100+ full so we don't print extra really-narrow/high bar if percent >= 100.0: diff --git a/src/allmydata/test/cli/test_status.py b/src/allmydata/test/cli/test_status.py index 551b1a3e0..663418219 100644 --- a/src/allmydata/test/cli/test_status.py +++ b/src/allmydata/test/cli/test_status.py @@ -4,7 +4,6 @@ import json import tempfile from six.moves import StringIO from os.path import join -from UserDict import UserDict from twisted.trial import unittest from twisted.internet import defer @@ -60,9 +59,8 @@ class ProgressBar(unittest.TestCase): ) -class _FakeOptions(UserDict, object): +class _FakeOptions(dict): def __init__(self): - super(_FakeOptions, self).__init__() self._tmp = tempfile.mkdtemp() os.mkdir(join(self._tmp, 'private'), 0o777) with open(join(self._tmp, 'private', 'api_auth_token'), 'w') as f: From 919930173e167c5bf03cdc7de5c3c352f4b58c23 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 10:56:20 -0400 Subject: [PATCH 16/46] More progress towards Python 3. --- src/allmydata/test/cli/test_status.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/allmydata/test/cli/test_status.py b/src/allmydata/test/cli/test_status.py index 663418219..77eaddf7c 100644 --- a/src/allmydata/test/cli/test_status.py +++ b/src/allmydata/test/cli/test_status.py @@ -1,8 +1,7 @@ import os import mock -import json import tempfile -from six.moves import StringIO +from io import BytesIO, StringIO from os.path import join from twisted.trial import unittest @@ -21,6 +20,7 @@ from allmydata.immutable.downloader.status import DownloadStatus from allmydata.mutable.publish import PublishStatus from allmydata.mutable.retrieve import RetrieveStatus from allmydata.mutable.servermap import UpdateStatus +from allmydata.util import jsonbytes as json from ..no_network import GridTestMixin from ..common_web import do_http @@ -84,7 +84,7 @@ class Integration(GridTestMixin, CLITestMixin, unittest.TestCase): # upload something c0 = self.g.clients[0] - data = MutableData("data" * 100) + data = MutableData(b"data" * 100) filenode = yield c0.create_mutable_file(data) self.uri = filenode.get_uri() @@ -143,14 +143,14 @@ class CommandStatus(unittest.TestCase): def test_simple(self, http): recent_items = active_items = [ UploadStatus(), - DownloadStatus("abcd", 12345), + DownloadStatus(b"abcd", 12345), PublishStatus(), RetrieveStatus(), UpdateStatus(), FakeStatus(), ] values = [ - StringIO(json.dumps({ + BytesIO(json.dumps({ "active": list( marshal_json(item) for item @@ -161,15 +161,15 @@ class CommandStatus(unittest.TestCase): for item in recent_items ), - })), - StringIO(json.dumps({ + }).encode("utf-8")), + BytesIO(json.dumps({ "counters": { "bytes_downloaded": 0, }, "stats": { "node.uptime": 0, } - })), + }).encode("utf-8")), ] http.side_effect = lambda *args, **kw: values.pop(0) do_status(self.options) From 2f259509441c773cc713d2f753ab2b33c43908f8 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:00:49 -0400 Subject: [PATCH 17/46] All tests pass on Python 3. --- src/allmydata/scripts/common_http.py | 6 +++--- src/allmydata/scripts/tahoe_status.py | 2 +- src/allmydata/test/cli/test_status.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allmydata/scripts/common_http.py b/src/allmydata/scripts/common_http.py index 5d989feb1..20159b020 100644 --- a/src/allmydata/scripts/common_http.py +++ b/src/allmydata/scripts/common_http.py @@ -1,7 +1,7 @@ from __future__ import print_function import os -from six.moves import cStringIO as StringIO +from io import BytesIO from six.moves import urllib, http_client import six import allmydata # for __full_version__ @@ -39,8 +39,8 @@ class BadResponse(object): def do_http(method, url, body=""): - if isinstance(body, str): - body = StringIO(body) + if isinstance(body, bytes): + body = BytesIO(body) elif isinstance(body, six.text_type): raise TypeError("do_http body must be a bytestring, not unicode") else: diff --git a/src/allmydata/scripts/tahoe_status.py b/src/allmydata/scripts/tahoe_status.py index 36c2d5daa..ff746901b 100644 --- a/src/allmydata/scripts/tahoe_status.py +++ b/src/allmydata/scripts/tahoe_status.py @@ -32,7 +32,7 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None): body = '' if post_args is not None: raise ValueError("post_args= only valid for POST method") - resp = do_http(method, url, body=body) + resp = do_http(method, url, body=body.encode("utf-8")) if isinstance(resp, BadResponse): # specifically NOT using format_http_error() here because the # URL is pretty sensitive (we're doing /uri/). diff --git a/src/allmydata/test/cli/test_status.py b/src/allmydata/test/cli/test_status.py index 77eaddf7c..35cd26894 100644 --- a/src/allmydata/test/cli/test_status.py +++ b/src/allmydata/test/cli/test_status.py @@ -95,8 +95,8 @@ class Integration(GridTestMixin, CLITestMixin, unittest.TestCase): d = self.do_cli('status')# '--verbose') def _check(ign): - code, stdout, stdin = ign - self.assertEqual(code, 0) + code, stdout, stderr = ign + self.assertEqual(code, 0, stderr) self.assertTrue('Skipped 1' in stdout) d.addCallback(_check) return d From 2dfaa3ac2f0e6d7a5918e4395cd7bfffe51f536b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:08:23 -0400 Subject: [PATCH 18/46] Ported to Python 3. --- src/allmydata/test/cli/test_status.py | 21 +++++++++++++++++---- src/allmydata/util/_python3.py | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/allmydata/test/cli/test_status.py b/src/allmydata/test/cli/test_status.py index 35cd26894..a04939429 100644 --- a/src/allmydata/test/cli/test_status.py +++ b/src/allmydata/test/cli/test_status.py @@ -1,3 +1,16 @@ +""" +Ported to Python 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 +from six import ensure_text + import os import mock import tempfile @@ -122,18 +135,18 @@ class CommandStatus(unittest.TestCase): @mock.patch('sys.stdout', StringIO()) def test_no_operations(self, http): values = [ - StringIO(json.dumps({ + StringIO(ensure_text(json.dumps({ "active": [], "recent": [], - })), - StringIO(json.dumps({ + }))), + StringIO(ensure_text(json.dumps({ "counters": { "bytes_downloaded": 0, }, "stats": { "node.uptime": 0, } - })), + }))), ] http.side_effect = lambda *args, **kw: values.pop(0) do_status(self.options) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 2944592bd..42e04135a 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -142,6 +142,7 @@ PORTED_MODULES = [ PORTED_TEST_MODULES = [ "allmydata.test.cli.test_alias", "allmydata.test.cli.test_create", + "allmydata.test.cli.test_status", "allmydata.test.mutable.test_checker", "allmydata.test.mutable.test_datahandle", From 2a212dc4c4a0a2755d96cd6d3e0f3947c3c1ee41 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:29:30 -0400 Subject: [PATCH 19/46] News file. --- newsfragments/3640.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3640.minor diff --git a/newsfragments/3640.minor b/newsfragments/3640.minor new file mode 100644 index 000000000..e69de29bb From 045b6085ff5ce6190b72b82814076ad472fe5ba5 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:30:48 -0400 Subject: [PATCH 20/46] Port to Python 3. --- src/allmydata/test/test_i2p_provider.py | 12 ++++++++++++ src/allmydata/util/_python3.py | 1 + 2 files changed, 13 insertions(+) diff --git a/src/allmydata/test/test_i2p_provider.py b/src/allmydata/test/test_i2p_provider.py index 37f2333f5..364a85c5b 100644 --- a/src/allmydata/test/test_i2p_provider.py +++ b/src/allmydata/test/test_i2p_provider.py @@ -1,3 +1,15 @@ +""" +Ported to Python 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + import os from twisted.trial import unittest from twisted.internet import defer, error diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 14ca60e0a..416a2e612 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -189,6 +189,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_helper", "allmydata.test.test_humanreadable", "allmydata.test.test_hung_server", + "allmydata.test.test_i2p_provider", "allmydata.test.test_immutable", "allmydata.test.test_introducer", "allmydata.test.test_iputil", From 334093e28c7bf391beb5efe6928cf060542cb9b5 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:49:27 -0400 Subject: [PATCH 21/46] All tests pass on Python 3. --- src/allmydata/test/test_tor_provider.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allmydata/test/test_tor_provider.py b/src/allmydata/test/test_tor_provider.py index f5dd2e29c..b05879228 100644 --- a/src/allmydata/test/test_tor_provider.py +++ b/src/allmydata/test/test_tor_provider.py @@ -173,7 +173,7 @@ class CreateOnion(unittest.TestCase): protocol))) txtorcon = mock.Mock() ehs = mock.Mock() - ehs.private_key = "privkey" + ehs.private_key = b"privkey" ehs.hostname = "ONION.onion" txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs) ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None)) @@ -208,7 +208,7 @@ class CreateOnion(unittest.TestCase): fn = os.path.join(basedir, tahoe_config_tor["onion.private_key_file"]) with open(fn, "rb") as f: privkey = f.read() - self.assertEqual(privkey, "privkey") + self.assertEqual(privkey, b"privkey") def test_launch(self): return self._do_test_launch(None) @@ -227,7 +227,7 @@ class CreateOnion(unittest.TestCase): protocol))) txtorcon = mock.Mock() ehs = mock.Mock() - ehs.private_key = "privkey" + ehs.private_key = b"privkey" ehs.hostname = "ONION.onion" txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs) ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None)) @@ -259,7 +259,7 @@ class CreateOnion(unittest.TestCase): fn = os.path.join(basedir, tahoe_config_tor["onion.private_key_file"]) with open(fn, "rb") as f: privkey = f.read() - self.assertEqual(privkey, "privkey") + self.assertEqual(privkey, b"privkey") _None = object() @@ -590,7 +590,7 @@ class Provider_Service(unittest.TestCase): launch_tor.assert_called_with(reactor, None, os.path.join(basedir, "private"), txtorcon) txtorcon.EphemeralHiddenService.assert_called_with("456 127.0.0.1:123", - "private key") + b"private key") ehs.add_to_tor.assert_called_with(tor_state.protocol) yield p.stopService() @@ -632,7 +632,7 @@ class Provider_Service(unittest.TestCase): cfs.assert_called_with(reactor, "ep_desc") txtorcon.build_tor_connection.assert_called_with(tcep) txtorcon.EphemeralHiddenService.assert_called_with("456 127.0.0.1:123", - "private key") + b"private key") ehs.add_to_tor.assert_called_with(tor_state.protocol) yield p.stopService() From 3ef69f2738d2213cf9039e7dbfa1dccc82f85ba6 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:51:25 -0400 Subject: [PATCH 22/46] Port to Python 3. --- src/allmydata/test/test_tor_provider.py | 12 ++++++++++++ src/allmydata/util/_python3.py | 1 + 2 files changed, 13 insertions(+) diff --git a/src/allmydata/test/test_tor_provider.py b/src/allmydata/test/test_tor_provider.py index b05879228..148d813f5 100644 --- a/src/allmydata/test/test_tor_provider.py +++ b/src/allmydata/test/test_tor_provider.py @@ -1,3 +1,15 @@ +""" +Ported to Python 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + import os from twisted.trial import unittest from twisted.internet import defer, error diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 416a2e612..bdaa48cd1 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -219,6 +219,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_testing", "allmydata.test.test_time_format", + "allmydata.test.test_tor_provider", "allmydata.test.test_upload", "allmydata.test.test_uri", "allmydata.test.test_util", From be25d578c538f5c07cdcf78f4c6b677fc14c7c00 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:53:07 -0400 Subject: [PATCH 23/46] Port to Python 3. --- src/allmydata/test/test_connections.py | 11 +++++++++++ src/allmydata/util/_python3.py | 1 + 2 files changed, 12 insertions(+) diff --git a/src/allmydata/test/test_connections.py b/src/allmydata/test/test_connections.py index 7a24ac794..5816afdab 100644 --- a/src/allmydata/test/test_connections.py +++ b/src/allmydata/test/test_connections.py @@ -1,3 +1,14 @@ +""" +Ported to Python 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from twisted.trial import unittest from twisted.internet import reactor diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index bdaa48cd1..00399333c 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -167,6 +167,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_codec", "allmydata.test.test_common_util", "allmydata.test.test_configutil", + "allmydata.test.test_connections", "allmydata.test.test_connection_status", "allmydata.test.test_crawler", "allmydata.test.test_crypto", From e148fef19fd0321c1e092f89a44b6477b3c344cf Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:55:42 -0400 Subject: [PATCH 24/46] Port to Python 3. --- src/allmydata/util/_python3.py | 1 + src/allmydata/util/tor_provider.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 00399333c..921cfef7c 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -123,6 +123,7 @@ PORTED_MODULES = [ "allmydata.util.spans", "allmydata.util.statistics", "allmydata.util.time_format", + "allmydata.util.tor_provider", "allmydata.web.check_results", "allmydata.web.common", "allmydata.web.directory", diff --git a/src/allmydata/util/tor_provider.py b/src/allmydata/util/tor_provider.py index 7b832735d..c4c63f61a 100644 --- a/src/allmydata/util/tor_provider.py +++ b/src/allmydata/util/tor_provider.py @@ -1,5 +1,15 @@ # -*- coding: utf-8 -*- +""" +Ported to Python 3. +""" from __future__ import absolute_import, print_function, with_statement +from __future__ import division +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + import os from zope.interface import ( From 185face21f3da34e052fde7c014f850996003f29 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 11:57:07 -0400 Subject: [PATCH 25/46] Port to Python 3. --- src/allmydata/util/_python3.py | 1 + src/allmydata/util/i2p_provider.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 921cfef7c..f9c51eec3 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -110,6 +110,7 @@ PORTED_MODULES = [ "allmydata.util.happinessutil", "allmydata.util.hashutil", "allmydata.util.humanreadable", + "allmydata.util.i2p_provider", "allmydata.util.idlib", "allmydata.util.iputil", "allmydata.util.jsonbytes", diff --git a/src/allmydata/util/i2p_provider.py b/src/allmydata/util/i2p_provider.py index 22575b4ca..071245adf 100644 --- a/src/allmydata/util/i2p_provider.py +++ b/src/allmydata/util/i2p_provider.py @@ -1,5 +1,15 @@ # -*- coding: utf-8 -*- +""" +Ported to Python 3. +""" from __future__ import absolute_import, print_function, with_statement +from __future__ import division +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + import os from zope.interface import ( From f9d0116283fea817dc1277a1d8c1832995715961 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 13:29:50 -0400 Subject: [PATCH 26/46] Fix typo. --- src/allmydata/scripts/tahoe_add_alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/scripts/tahoe_add_alias.py b/src/allmydata/scripts/tahoe_add_alias.py index 5faa0dae1..19474b9e8 100644 --- a/src/allmydata/scripts/tahoe_add_alias.py +++ b/src/allmydata/scripts/tahoe_add_alias.py @@ -171,7 +171,7 @@ def list_aliases(options): if options['json']: dumped = json.dumps(data, indent=4) if isinstance(dumped, bytes): - loaded = dumped.decode("utf-8") + dumped = dumped.decode("utf-8") output = _escape_format(dumped) else: def dircap(details): From e091d4d0aab527b03287d59eccb58e3d7fce6538 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 18 Mar 2021 13:29:56 -0400 Subject: [PATCH 27/46] Body should be bytes. --- src/allmydata/scripts/common_http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/scripts/common_http.py b/src/allmydata/scripts/common_http.py index 20159b020..a53f6baa8 100644 --- a/src/allmydata/scripts/common_http.py +++ b/src/allmydata/scripts/common_http.py @@ -38,7 +38,7 @@ class BadResponse(object): return "" -def do_http(method, url, body=""): +def do_http(method, url, body=b""): if isinstance(body, bytes): body = BytesIO(body) elif isinstance(body, six.text_type): From 70291cd468b2ee5de6dab574508fdee3aa4d5d1a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 19 Mar 2021 15:55:19 -0400 Subject: [PATCH 28/46] Add "docs" job to CircleCI workflow --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2daf3b93f..1fb7558de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,6 +89,9 @@ workflows: - "typechecks": <<: *DOCKERHUB_CONTEXT + - "docs": + <<: *DOCKERHUB_CONTEXT + images: # Build the Docker images used by the ci jobs. This makes the ci jobs # faster and takes various spurious failures out of the critical path. From 3d6931a0643326cd7c32977303df41382a7ce983 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 09:54:12 -0400 Subject: [PATCH 29/46] News file. --- newsfragments/3646.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3646.minor diff --git a/newsfragments/3646.minor b/newsfragments/3646.minor new file mode 100644 index 000000000..e69de29bb From b1b70cc61cb7d443846f9760a89924a6c2f15924 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:02:01 -0400 Subject: [PATCH 30/46] Tests pass on Python 3. --- src/allmydata/scripts/backupdb.py | 4 +-- src/allmydata/test/test_backupdb.py | 49 +++++++++++++++-------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/allmydata/scripts/backupdb.py b/src/allmydata/scripts/backupdb.py index d188eec3c..1bffbbfc3 100644 --- a/src/allmydata/scripts/backupdb.py +++ b/src/allmydata/scripts/backupdb.py @@ -303,8 +303,8 @@ class BackupDB_v2(object): for name in contents: entries.append( [name.encode("utf-8"), contents[name]] ) entries.sort() - data = "".join([netstring(name_utf8)+netstring(cap) - for (name_utf8,cap) in entries]) + data = b"".join([netstring(name_utf8)+netstring(cap) + for (name_utf8,cap) in entries]) dirhash = backupdb_dirhash(data) dirhash_s = base32.b2a(dirhash) c = self.cursor diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/test_backupdb.py index 04b264d39..ffad49265 100644 --- a/src/allmydata/test/test_backupdb.py +++ b/src/allmydata/test/test_backupdb.py @@ -1,4 +1,5 @@ from __future__ import print_function +from past.builtins import unicode import sys import os.path, time @@ -87,15 +88,15 @@ class BackupDB(unittest.TestCase): r = bdb.check_file(foo_fn) self.failUnlessEqual(r.was_uploaded(), False) - r.did_upload("foo-cap") + r.did_upload(b"foo-cap") r = bdb.check_file(blah_fn) self.failUnlessEqual(r.was_uploaded(), False) r.did_upload("blah-cap") r = bdb.check_file(foo_fn) - self.failUnlessEqual(r.was_uploaded(), "foo-cap") - self.failUnlessEqual(type(r.was_uploaded()), str) + self.failUnlessEqual(r.was_uploaded(), b"foo-cap") + self.failUnlessEqual(type(r.was_uploaded()), bytes) self.failUnlessEqual(r.should_check(), False) time.sleep(1.0) # make sure the timestamp changes @@ -103,28 +104,28 @@ class BackupDB(unittest.TestCase): r = bdb.check_file(foo_fn) self.failUnlessEqual(r.was_uploaded(), False) - r.did_upload("new-cap") + r.did_upload(b"new-cap") r = bdb.check_file(foo_fn) - self.failUnlessEqual(r.was_uploaded(), "new-cap") + self.failUnlessEqual(r.was_uploaded(), b"new-cap") self.failUnlessEqual(r.should_check(), False) # if we spontaneously decide to upload it anyways, nothing should # break - r.did_upload("new-cap") + r.did_upload(b"new-cap") r = bdb.check_file(foo_fn, use_timestamps=False) self.failUnlessEqual(r.was_uploaded(), False) - r.did_upload("new-cap") + r.did_upload(b"new-cap") r = bdb.check_file(foo_fn) - self.failUnlessEqual(r.was_uploaded(), "new-cap") + self.failUnlessEqual(r.was_uploaded(), b"new-cap") self.failUnlessEqual(r.should_check(), False) bdb.NO_CHECK_BEFORE = 0 bdb.ALWAYS_CHECK_AFTER = 0.1 r = bdb.check_file(blah_fn) - self.failUnlessEqual(r.was_uploaded(), "blah-cap") + self.failUnlessEqual(r.was_uploaded(), b"blah-cap") self.failUnlessEqual(r.should_check(), True) r.did_check_healthy("results") # we know they're ignored for now @@ -132,7 +133,7 @@ class BackupDB(unittest.TestCase): bdb.ALWAYS_CHECK_AFTER = 400 r = bdb.check_file(blah_fn) - self.failUnlessEqual(r.was_uploaded(), "blah-cap") + self.failUnlessEqual(r.was_uploaded(), b"blah-cap") self.failUnlessEqual(r.should_check(), False) os.unlink(os.path.join(basedir, "foo.txt")) @@ -165,13 +166,13 @@ class BackupDB(unittest.TestCase): dbfile = os.path.join(basedir, "dbfile") bdb = self.create(dbfile) - contents = {u"file1": "URI:CHK:blah1", - u"file2": "URI:CHK:blah2", - u"dir1": "URI:DIR2-CHK:baz2"} + contents = {u"file1": b"URI:CHK:blah1", + u"file2": b"URI:CHK:blah2", + u"dir1": b"URI:DIR2-CHK:baz2"} r = bdb.check_directory(contents) self.failUnless(isinstance(r, backupdb.DirectoryResult)) self.failIf(r.was_created()) - dircap = "URI:DIR2-CHK:foo1" + dircap = b"URI:DIR2-CHK:foo1" r.did_create(dircap) r = bdb.check_directory(contents) @@ -185,7 +186,7 @@ class BackupDB(unittest.TestCase): r = bdb.check_directory(contents) self.failUnless(r.was_created()) self.failUnlessEqual(r.was_created(), dircap) - self.failUnlessEqual(type(r.was_created()), str) + self.failUnlessEqual(type(r.was_created()), bytes) self.failUnlessEqual(r.should_check(), False) bdb.NO_CHECK_BEFORE = 0 @@ -207,14 +208,14 @@ class BackupDB(unittest.TestCase): self.failUnlessEqual(r.should_check(), False) - contents2 = {u"file1": "URI:CHK:blah1", - u"dir1": "URI:DIR2-CHK:baz2"} + contents2 = {u"file1": b"URI:CHK:blah1", + u"dir1": b"URI:DIR2-CHK:baz2"} r = bdb.check_directory(contents2) self.failIf(r.was_created()) - contents3 = {u"file1": "URI:CHK:blah1", - u"file2": "URI:CHK:blah3", - u"dir1": "URI:DIR2-CHK:baz2"} + contents3 = {u"file1": b"URI:CHK:blah1", + u"file2": b"URI:CHK:blah3", + u"dir1": b"URI:DIR2-CHK:baz2"} r = bdb.check_directory(contents3) self.failIf(r.was_created()) @@ -235,10 +236,10 @@ class BackupDB(unittest.TestCase): r = bdb.check_file(foo_fn) self.failUnlessEqual(r.was_uploaded(), False) - r.did_upload("foo-cap") + r.did_upload(b"foo-cap") r = bdb.check_file(foo_fn) - self.failUnlessEqual(r.was_uploaded(), "foo-cap") + self.failUnlessEqual(r.was_uploaded(), b"foo-cap") self.failUnlessEqual(r.should_check(), False) bar_fn = self.writeto(u"b\u00e5r.txt", "bar.txt") @@ -246,9 +247,9 @@ class BackupDB(unittest.TestCase): r = bdb.check_file(bar_fn) self.failUnlessEqual(r.was_uploaded(), False) - r.did_upload("bar-cap") + r.did_upload(b"bar-cap") r = bdb.check_file(bar_fn) - self.failUnlessEqual(r.was_uploaded(), "bar-cap") + self.failUnlessEqual(r.was_uploaded(), b"bar-cap") self.failUnlessEqual(r.should_check(), False) From f0e634af4f031d61c7d1b99c35a43fb620896b7f Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:02:42 -0400 Subject: [PATCH 31/46] Move to correct directory. --- src/allmydata/test/{ => cli}/test_backupdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/allmydata/test/{ => cli}/test_backupdb.py (99%) diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/cli/test_backupdb.py similarity index 99% rename from src/allmydata/test/test_backupdb.py rename to src/allmydata/test/cli/test_backupdb.py index ffad49265..6651e6da5 100644 --- a/src/allmydata/test/test_backupdb.py +++ b/src/allmydata/test/cli/test_backupdb.py @@ -9,7 +9,7 @@ from twisted.trial import unittest from allmydata.util import fileutil from allmydata.util.encodingutil import listdir_unicode from allmydata.scripts import backupdb -from .common_util import skip_if_cannot_represent_filename +from ..common_util import skip_if_cannot_represent_filename class BackupDB(unittest.TestCase): def create(self, dbfile): From 8396e9e24d27800b9e6ed6ef20ca7accf7b33711 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:07:24 -0400 Subject: [PATCH 32/46] Port to Python 3. --- src/allmydata/test/cli/test_backupdb.py | 16 +++++++++++++--- src/allmydata/util/_python3.py | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/cli/test_backupdb.py b/src/allmydata/test/cli/test_backupdb.py index 6651e6da5..665382dc8 100644 --- a/src/allmydata/test/cli/test_backupdb.py +++ b/src/allmydata/test/cli/test_backupdb.py @@ -1,5 +1,15 @@ +""" +Ported to Python 3. +""" from __future__ import print_function -from past.builtins import unicode +from __future__ import absolute_import +from __future__ import division +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + # Don't import future bytes so we don't break a couple of tests + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, str, max, min # noqa: F401 import sys import os.path, time @@ -71,7 +81,7 @@ class BackupDB(unittest.TestCase): def writeto(self, filename, data): - fn = os.path.join(self.basedir, unicode(filename)) + fn = os.path.join(self.basedir, filename) parentdir = os.path.dirname(fn) fileutil.make_dirs(parentdir) fileutil.write(fn, data) @@ -229,7 +239,7 @@ class BackupDB(unittest.TestCase): bdb = self.create(dbfile) self.writeto(u"f\u00f6\u00f6.txt", "foo.txt") - files = [fn for fn in listdir_unicode(unicode(basedir)) if fn.endswith(".txt")] + files = [fn for fn in listdir_unicode(str(basedir)) if fn.endswith(".txt")] self.failUnlessEqual(len(files), 1) foo_fn = os.path.join(basedir, files[0]) #print(foo_fn, type(foo_fn)) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 8cf6a61b3..5478245ae 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -144,6 +144,7 @@ PORTED_MODULES = [ PORTED_TEST_MODULES = [ "allmydata.test.cli.test_alias", + "allmydata.test.cli.test_backupdb", "allmydata.test.cli.test_create", "allmydata.test.cli.test_status", From 4005d90024bac064a89fd1ad09802a302bd5c7d7 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:42:29 -0400 Subject: [PATCH 33/46] Ensure the fake matches the real Wormhole interface. --- src/allmydata/test/cli/test_invite.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index f356e18de..05bfed128 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -16,6 +16,8 @@ class _FakeWormhole(object): def __init__(self, outgoing_messages): self.messages = [] + for o in outgoing_messages: + assert isinstance(o, bytes) self._outgoing = outgoing_messages def get_code(self): @@ -26,15 +28,16 @@ class _FakeWormhole(object): def get_welcome(self): return defer.succeed( - json.dumps({ + { u"welcome": {}, - }) + } ) def allocate_code(self): return None def send_message(self, msg): + assert isinstance(msg, bytes) self.messages.append(msg) def get_message(self): From d182ba82838c3dce6113d6c9b106981cff4ec1ce Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:53:10 -0400 Subject: [PATCH 34/46] Utility to dump JSON to bytes. --- src/allmydata/test/test_util.py | 8 +++++++- src/allmydata/util/jsonbytes.py | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 5f5db82bd..a14adb787 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -507,7 +507,6 @@ class JSONBytes(unittest.TestCase): self.assertEqual(json.loads(encoded), expected) self.assertEqual(jsonbytes.loads(encoded), expected) - def test_encode_unicode(self): """BytesJSONEncoder encodes Unicode string as usual.""" expected = { @@ -515,3 +514,10 @@ class JSONBytes(unittest.TestCase): } encoded = jsonbytes.dumps(expected) self.assertEqual(json.loads(encoded), expected) + + def test_dumps_bytes(self): + """jsonbytes.dumps_bytes always returns bytes.""" + x = {u"def\N{SNOWMAN}\uFF00": 123} + encoded = jsonbytes.dumps_bytes(x) + self.assertIsInstance(encoded, bytes) + self.assertEqual(json.loads(encoded, encoding="utf-8"), x) diff --git a/src/allmydata/util/jsonbytes.py b/src/allmydata/util/jsonbytes.py index 935187d29..c46a932d0 100644 --- a/src/allmydata/util/jsonbytes.py +++ b/src/allmydata/util/jsonbytes.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from future.utils import PY2 +from future.utils import PY2, PY3 if PY2: from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 @@ -51,6 +51,14 @@ def dumps(obj, *args, **kwargs): return json.dumps(obj, cls=BytesJSONEncoder, *args, **kwargs) +def dumps_bytes(obj, *args, **kwargs): + """Encode to JSON, then encode as bytes.""" + result = dumps(obj, *args, **kwargs) + if PY3: + result = result.encode("utf-8") + return result + + # To make this module drop-in compatible with json module: loads = json.loads From e140dc06ea2d5c2dc77f2001acaff4535513bf61 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:53:53 -0400 Subject: [PATCH 35/46] Tests pass on Python 3. --- src/allmydata/scripts/create_node.py | 7 +++---- src/allmydata/scripts/tahoe_invite.py | 7 +++---- src/allmydata/test/cli/test_invite.py | 6 ++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py index 3afbeeb0d..9bcfc2fe0 100644 --- a/src/allmydata/scripts/create_node.py +++ b/src/allmydata/scripts/create_node.py @@ -5,13 +5,12 @@ from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals -from future.utils import PY2 +from future.utils import PY2, PY3 if PY2: from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 import io import os -import json try: from allmydata.scripts.types_ import SubCommands @@ -32,7 +31,7 @@ from allmydata.scripts.common import ( from allmydata.scripts.default_nodedir import _default_nodedir from allmydata.util.assertutil import precondition from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_local_unicode_path, get_io_encoding -from allmydata.util import fileutil, i2p_provider, iputil, tor_provider +from allmydata.util import fileutil, i2p_provider, iputil, tor_provider, jsonbytes as json from wormhole import wormhole @@ -389,7 +388,7 @@ def _get_config_via_wormhole(config): "client-v1": {}, } } - wh.send_message(json.dumps(intro)) + wh.send_message(json.dumps_bytes(intro)) server_intro = yield wh.get_message() server_intro = json.loads(server_intro) diff --git a/src/allmydata/scripts/tahoe_invite.py b/src/allmydata/scripts/tahoe_invite.py index 884536ec2..403481029 100644 --- a/src/allmydata/scripts/tahoe_invite.py +++ b/src/allmydata/scripts/tahoe_invite.py @@ -1,7 +1,5 @@ from __future__ import print_function -import json - try: from allmydata.scripts.types_ import SubCommands except ImportError: @@ -13,6 +11,7 @@ from twisted.internet import defer, reactor from wormhole import wormhole from allmydata.util.encodingutil import argv_to_abspath +from allmydata.util import jsonbytes as json from allmydata.scripts.common import get_default_nodedir, get_introducer_furl from allmydata.node import read_config @@ -54,7 +53,7 @@ def _send_config_via_wormhole(options, config): code = yield wh.get_code() print("Invite Code for client: {}".format(code), file=out) - wh.send_message(json.dumps({ + wh.send_message(json.dumps_bytes({ u"abilities": { u"server-v1": {}, } @@ -71,7 +70,7 @@ def _send_config_via_wormhole(options, config): defer.returnValue(1) print(" transmitting configuration", file=out) - wh.send_message(json.dumps(config)) + wh.send_message(json.dumps_bytes(config)) yield wh.close() diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index 05bfed128..452acbc9c 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -1,3 +1,5 @@ +from past.builtins import unicode + import os import mock import json @@ -48,6 +50,10 @@ class _FakeWormhole(object): def _create_fake_wormhole(outgoing_messages): + outgoing_messages = [ + m.encode("utf-8") if isinstance(m, unicode) else m + for m in outgoing_messages + ] return _FakeWormhole(outgoing_messages) From 15d6ab610da3f9fb62d491fca5c5dd0e50e6e7ad Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 10:55:14 -0400 Subject: [PATCH 36/46] Port to Python 3. --- src/allmydata/test/cli/test_invite.py | 14 ++++++++++++-- src/allmydata/util/_python3.py | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index 452acbc9c..36021af13 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -1,4 +1,14 @@ -from past.builtins import unicode +""" +Ported to Pythn 3. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 import os import mock @@ -51,7 +61,7 @@ class _FakeWormhole(object): def _create_fake_wormhole(outgoing_messages): outgoing_messages = [ - m.encode("utf-8") if isinstance(m, unicode) else m + m.encode("utf-8") if isinstance(m, str) else m for m in outgoing_messages ] return _FakeWormhole(outgoing_messages) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 8cf6a61b3..5a29020b8 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -145,6 +145,7 @@ PORTED_MODULES = [ PORTED_TEST_MODULES = [ "allmydata.test.cli.test_alias", "allmydata.test.cli.test_create", + "allmydata.test.cli.test_invite", "allmydata.test.cli.test_status", "allmydata.test.mutable.test_checker", From 7e889eb60a41382b307dce023ca6076855ceabb9 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 11:02:14 -0400 Subject: [PATCH 37/46] news file. --- newsfragments/3647.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3647.minor diff --git a/newsfragments/3647.minor b/newsfragments/3647.minor new file mode 100644 index 000000000..e69de29bb From 4b0aa412567a1d47c4d0be6c46c242a558036109 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 23 Mar 2021 11:04:00 -0400 Subject: [PATCH 38/46] Fix flake. --- src/allmydata/scripts/create_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py index 9bcfc2fe0..0258a0074 100644 --- a/src/allmydata/scripts/create_node.py +++ b/src/allmydata/scripts/create_node.py @@ -5,7 +5,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals -from future.utils import PY2, PY3 +from future.utils import PY2 if PY2: from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 From 9a066d29d4cade860278e44e49e85249573e44ff Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 23 Mar 2021 15:25:00 -0600 Subject: [PATCH 39/46] remove LGTM --- .lgtm.yml | 22 ---------------------- newsfragments/3649.minor | 0 2 files changed, 22 deletions(-) delete mode 100644 .lgtm.yml create mode 100644 newsfragments/3649.minor diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index efc2479ca..000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,22 +0,0 @@ -extraction: - python: - after_prepare: - - | - # https://discuss.lgtm.com/t/determination-of-python-requirements/974/4 - sed -i 's/\("pyOpenSSL\)/\# Dependency removed for lgtm (see .lgtm.yml): \1/g' src/allmydata/_auto_deps.py - -queries: - # This generates spurious errors for calls by interface because of the - # zope.interface choice to exclude self from method signatures. So, turn it - # off. - - exclude: "py/call/wrong-arguments" - - # The premise of this query is broken. The errors it produces are nonsense. - # There is no such thing as a "procedure" in Python and "None" is not - # meaningless. - - exclude: "py/procedure-return-value-used" - - # It is true that this query identifies things which are sometimes mistakes. - # However, it also identifies things which are entirely valid. Therefore, - # it produces noisy results. - - exclude: "py/implicit-string-concatenation-in-list" \ No newline at end of file diff --git a/newsfragments/3649.minor b/newsfragments/3649.minor new file mode 100644 index 000000000..e69de29bb From 20d9b84b0ed9dbdc132c44b1b18fc73ba4e6710f Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 24 Mar 2021 13:14:58 -0400 Subject: [PATCH 40/46] news fragment --- newsfragments/3650.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3650.bugfix diff --git a/newsfragments/3650.bugfix b/newsfragments/3650.bugfix new file mode 100644 index 000000000..a747a1246 --- /dev/null +++ b/newsfragments/3650.bugfix @@ -0,0 +1 @@ +``tahoe invite`` will now read share encoding/placement configuration from a Tahoe client node configuration file if they are not given on the command line, instead of raising an unhandled exception. From 3963979fbd14f5bdf8041018c762ff85720e82ec Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 24 Mar 2021 13:23:19 -0400 Subject: [PATCH 41/46] Test and fix `tahoe invite` w/o share arguments --- src/allmydata/scripts/tahoe_invite.py | 8 +- src/allmydata/test/cli/test_invite.py | 113 +++++++++++++++++++------- 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/src/allmydata/scripts/tahoe_invite.py b/src/allmydata/scripts/tahoe_invite.py index 884536ec2..5ef05e2ac 100644 --- a/src/allmydata/scripts/tahoe_invite.py +++ b/src/allmydata/scripts/tahoe_invite.py @@ -14,7 +14,7 @@ from wormhole import wormhole from allmydata.util.encodingutil import argv_to_abspath from allmydata.scripts.common import get_default_nodedir, get_introducer_furl -from allmydata.node import read_config +from allmydata.client import read_config class InviteOptions(usage.Options): @@ -94,9 +94,9 @@ def invite(options): nick = options['nick'] remote_config = { - "shares-needed": options["shares-needed"] or config.get('client', 'shares.needed'), - "shares-total": options["shares-total"] or config.get('client', 'shares.total'), - "shares-happy": options["shares-happy"] or config.get('client', 'shares.happy'), + "shares-needed": options["shares-needed"] or config.get_config('client', 'shares.needed'), + "shares-total": options["shares-total"] or config.get_config('client', 'shares.total'), + "shares-happy": options["shares-happy"] or config.get_config('client', 'shares.happy'), "nickname": nick, "introducer": introducer_furl, } diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index f356e18de..cc610d409 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -3,6 +3,11 @@ import mock import json from os.path import join +try: + from typing import Optional, Tuple +except ImportError: + pass + from twisted.trial import unittest from twisted.internet import defer from ..common_util import run_cli @@ -144,10 +149,17 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): intro_dir, ) - @defer.inlineCallbacks - def test_invite_success(self): + + def _invite_success(self, extra_args=(), tahoe_config=None): + # type: (Tuple[bytes], Optional[bytes]) -> defer.Deferred """ - successfully send an invite + Exercise an expected-success case of ``tahoe invite``. + + :param extra_args: Positional arguments to pass to ``tahoe invite`` + before the nickname. + + :param tahoe_config: If given, bytes to write to the node's + ``tahoe.cfg`` before running ``tahoe invite. """ intro_dir = os.path.join(self.basedir, "introducer") # we've never run the introducer, so it hasn't created @@ -155,6 +167,9 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): priv_dir = join(intro_dir, "private") with open(join(priv_dir, "introducer.furl"), "w") as f: f.write("pb://fooblam\n") + if tahoe_config is not None: + with open(join(intro_dir, "tahoe.cfg"), "w") as f: + f.write(tahoe_config) with mock.patch('allmydata.scripts.tahoe_invite.wormhole') as w: fake_wh = _create_fake_wormhole([ @@ -162,34 +177,76 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): ]) w.create = mock.Mock(return_value=fake_wh) - rc, out, err = yield run_cli( + def done(result): + rc, out, err = result + self.assertEqual(2, len(fake_wh.messages)) + self.assertEqual( + json.loads(fake_wh.messages[0]), + { + "abilities": + { + "server-v1": {} + }, + }, + ) + invite = json.loads(fake_wh.messages[1]) + self.assertEqual( + invite["nickname"], "foo", + ) + self.assertEqual( + invite["introducer"], "pb://fooblam", + ) + return invite + d = run_cli( "-d", intro_dir, "invite", - "--shares-needed", "1", - "--shares-happy", "1", - "--shares-total", "1", - "foo", - ) - self.assertEqual(2, len(fake_wh.messages)) - self.assertEqual( - json.loads(fake_wh.messages[0]), - { - "abilities": - { - "server-v1": {} - }, - }, - ) - self.assertEqual( - json.loads(fake_wh.messages[1]), - { - "shares-needed": "1", - "shares-total": "1", - "nickname": "foo", - "introducer": "pb://fooblam", - "shares-happy": "1", - }, + *(extra_args + ("foo",)) ) + d.addCallback(done) + return d + + @defer.inlineCallbacks + def test_invite_success(self): + """ + successfully send an invite + """ + invite = yield self._invite_success(( + "--shares-needed", "1", + "--shares-happy", "2", + "--shares-total", "3", + )) + self.assertEqual( + invite["shares-needed"], "1", + ) + self.assertEqual( + invite["shares-happy"], "2", + ) + self.assertEqual( + invite["shares-total"], "3", + ) + + @defer.inlineCallbacks + def test_invite_success_read_share_config(self): + """ + If ``--shares-{needed,happy,total}`` are not given on the command line + then the invitation is generated using the configured values. + """ + invite = yield self._invite_success(tahoe_config=""" +[client] +shares.needed = 2 +shares.happy = 4 +shares.total = 6 +""") + self.assertEqual( + invite["shares-needed"], "2", + ) + self.assertEqual( + invite["shares-happy"], "4", + ) + self.assertEqual( + invite["shares-total"], "6", + ) + @defer.inlineCallbacks def test_invite_no_furl(self): From 00de3b9e7e9abed7ab41887105516750233613ca Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 24 Mar 2021 13:54:10 -0400 Subject: [PATCH 42/46] type cleanups --- src/allmydata/test/cli/test_invite.py | 35 +++++++++++++++------------ 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index cc610d409..eae082a2b 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -4,7 +4,7 @@ import json from os.path import join try: - from typing import Optional, Tuple + from typing import Optional, Sequence except ImportError: pass @@ -149,9 +149,8 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): intro_dir, ) - def _invite_success(self, extra_args=(), tahoe_config=None): - # type: (Tuple[bytes], Optional[bytes]) -> defer.Deferred + # type: (Sequence[bytes], Optional[bytes]) -> defer.Deferred """ Exercise an expected-success case of ``tahoe invite``. @@ -165,11 +164,12 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): # we've never run the introducer, so it hasn't created # introducer.furl yet priv_dir = join(intro_dir, "private") - with open(join(priv_dir, "introducer.furl"), "w") as f: - f.write("pb://fooblam\n") + with open(join(priv_dir, "introducer.furl"), "w") as fobj_intro: + fobj_intro.write("pb://fooblam\n") if tahoe_config is not None: - with open(join(intro_dir, "tahoe.cfg"), "w") as f: - f.write(tahoe_config) + assert isinstance(tahoe_config, bytes) + with open(join(intro_dir, "tahoe.cfg"), "wb") as fobj_cfg: + fobj_cfg.write(tahoe_config) with mock.patch('allmydata.scripts.tahoe_invite.wormhole') as w: fake_wh = _create_fake_wormhole([ @@ -177,6 +177,14 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): ]) w.create = mock.Mock(return_value=fake_wh) + extra_args = tuple(extra_args) + + d = run_cli( + "-d", intro_dir, + "invite", + *(extra_args + ("foo",)) + ) + def done(result): rc, out, err = result self.assertEqual(2, len(fake_wh.messages)) @@ -197,11 +205,6 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): invite["introducer"], "pb://fooblam", ) return invite - d = run_cli( - "-d", intro_dir, - "invite", - *(extra_args + ("foo",)) - ) d.addCallback(done) return d @@ -211,9 +214,9 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): successfully send an invite """ invite = yield self._invite_success(( - "--shares-needed", "1", - "--shares-happy", "2", - "--shares-total", "3", + b"--shares-needed", b"1", + b"--shares-happy", b"2", + b"--shares-total", b"3", )) self.assertEqual( invite["shares-needed"], "1", @@ -231,7 +234,7 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): If ``--shares-{needed,happy,total}`` are not given on the command line then the invitation is generated using the configured values. """ - invite = yield self._invite_success(tahoe_config=""" + invite = yield self._invite_success(tahoe_config=b""" [client] shares.needed = 2 shares.happy = 4 From 03f27ae9eb7ad32eaf0452384dae9e544c9079dc Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 24 Mar 2021 15:58:00 -0400 Subject: [PATCH 43/46] fix grammar --- newsfragments/3650.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newsfragments/3650.bugfix b/newsfragments/3650.bugfix index a747a1246..09a810239 100644 --- a/newsfragments/3650.bugfix +++ b/newsfragments/3650.bugfix @@ -1 +1 @@ -``tahoe invite`` will now read share encoding/placement configuration from a Tahoe client node configuration file if they are not given on the command line, instead of raising an unhandled exception. +``tahoe invite`` will now read share encoding/placement configuration values from a Tahoe client node configuration file if they are not given on the command line, instead of raising an unhandled exception. From 415f3b286647fc4a88a848fdaca3cf029703f616 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Sat, 27 Mar 2021 10:46:33 -0400 Subject: [PATCH 44/46] Add newsfragment --- newsfragments/3654.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3654.minor diff --git a/newsfragments/3654.minor b/newsfragments/3654.minor new file mode 100644 index 000000000..e69de29bb From c392a334fc70dc83e56a3683d42434cc6fec8f52 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Sat, 27 Mar 2021 11:26:27 -0400 Subject: [PATCH 45/46] Pacify Python 3 `b"foo"` is `str` in Python 2, `bytes` in Python 3 --- src/allmydata/test/cli/test_invite.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/cli/test_invite.py b/src/allmydata/test/cli/test_invite.py index f3f8859b5..20d012995 100644 --- a/src/allmydata/test/cli/test_invite.py +++ b/src/allmydata/test/cli/test_invite.py @@ -233,9 +233,9 @@ class Invite(GridTestMixin, CLITestMixin, unittest.TestCase): successfully send an invite """ invite = yield self._invite_success(( - b"--shares-needed", b"1", - b"--shares-happy", b"2", - b"--shares-total", b"3", + "--shares-needed", "1", + "--shares-happy", "2", + "--shares-total", "3", )) self.assertEqual( invite["shares-needed"], "1", From 0485eeb126f9c6a1fe43e52d7cdf50e6587c4747 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Tue, 30 Mar 2021 10:03:03 -0400 Subject: [PATCH 46/46] Attempt to explain inclusion of lease secrets in the body --- docs/proposed/http-storage-node-protocol.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 36c718c56..dd778473e 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -234,6 +234,19 @@ Because of the simple types used throughout and the equivalence described in `RFC 7049`_ these examples should be representative regardless of which of these two encodings is chosen. +HTTP Design +~~~~~~~~~~~ + +The HTTP interface described here is informed by the ideas of REST +(Representational State Transfer). +For ``GET`` requests query parameters are preferred over values encoded in the request body. +For other requests query parameters are encoded into the message body. + +Many branches of the resource tree are conceived as homogenous containers: +one branch contains all of the share data; +another branch contains all of the lease data; +etc. + General ~~~~~~~ @@ -277,6 +290,9 @@ The lease expires after 31 days. Discussion `````````` +We considered an alternative where ``renew-secret`` and ``cancel-secret`` are placed in query arguments on the request path. +We chose to put these values into the request body to make the URL simpler. + Several behaviors here are blindly copied from the Foolscap-based storage server protocol. * There is a cancel secret but there is no API to use it to cancel a lease.