Merge lp:~jelmer/brz/hyper into lp:brz/3.1

Proposed by Jelmer Vernooij
Status: Work in progress
Proposed branch: lp:~jelmer/brz/hyper
Merge into: lp:brz/3.1
Prerequisite: lp:~jelmer/brz/split-http
Diff against target: 218 lines (+203/-0)
2 files modified
breezy/transport/__init__.py (+3/-0)
breezy/transport/http/httpx.py (+200/-0)
To merge this branch: bzr merge lp:~jelmer/brz/hyper
Reviewer Review Type Date Requested Status
Breezy developers Pending
Review via email: mp+395621@code.launchpad.net

Description of the change

Add a basic httpx transport.

To post a comment you must log in.

Unmerged revisions

7660. By Jelmer Vernooij

Add httpx transport.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/transport/__init__.py'
2--- breezy/transport/__init__.py 2020-12-26 19:28:58 +0000
3+++ breezy/transport/__init__.py 2020-12-26 19:28:58 +0000
4@@ -1668,6 +1668,9 @@
5 register_netloc=True)
6 register_lazy_transport('https+urllib://', 'breezy.transport.http.urllib',
7 'HttpTransport')
8+register_transport_proto('https+httpx://', register_netloc=True)
9+register_lazy_transport('https+httpx://', 'breezy.transport.http.httpx',
10+ 'HttpxTransport')
11 # Default http transports (last declared wins (if it can be imported))
12 register_transport_proto('http://',
13 help="Read-only access of branches exported on the web.")
14
15=== added file 'breezy/transport/http/httpx.py'
16--- breezy/transport/http/httpx.py 1970-01-01 00:00:00 +0000
17+++ breezy/transport/http/httpx.py 2020-12-26 19:28:58 +0000
18@@ -0,0 +1,200 @@
19+# Copyright (C) 2020 Breezy Developers
20+#
21+# This program is free software; you can redistribute it and/or modify
22+# it under the terms of the GNU General Public License as published by
23+# the Free Software Foundation; either version 2 of the License, or
24+# (at your option) any later version.
25+#
26+# This program is distributed in the hope that it will be useful,
27+# but WITHOUT ANY WARRANTY; without even the implied warranty of
28+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29+# GNU General Public License for more details.
30+#
31+# You should have received a copy of the GNU General Public License
32+# along with this program; if not, write to the Free Software
33+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34+
35+"""Implementation of Transport over http using httpx.
36+"""
37+
38+from __future__ import absolute_import
39+
40+from io import BytesIO
41+
42+from ... import errors
43+
44+try:
45+ import httpx
46+except ImportError as e:
47+ raise errors.DependencyNotPresent('httpx', e)
48+import re
49+
50+
51+from .. import ConnectedTransport
52+from . import default_user_agent
53+
54+
55+class HttpxTransport(ConnectedTransport):
56+ """HTTP Client implementations.
57+
58+ The protocol can be given as e.g. http+httpx://host/ to use a particular
59+ implementation.
60+ """
61+
62+ def __init__(self, base, _from_transport=None, ca_certs=None):
63+ """Set the base path where files will be stored."""
64+ proto_match = re.match(r'^(https?)(\+\w+)?://', base)
65+ if not proto_match:
66+ raise AssertionError("not a http url: %r" % base)
67+ self._unqualified_scheme = proto_match.group(1)
68+ super(HttpxTransport, self).__init__(
69+ base, _from_transport=_from_transport)
70+ connection = httpx.Client(
71+ headers={'User-Agent': default_user_agent()})
72+ self._set_connection(connection)
73+
74+ def disconnect(self):
75+ connection = self._get_connection()
76+ if connection is not None:
77+ connection.close()
78+
79+ def request(self, method, url):
80+ return self._get_connection().request(method, url)
81+
82+ def _remote_path(self, relpath):
83+ """See ConnectedTransport._remote_path.
84+
85+ user and passwords are not embedded in the path provided to the server.
86+ """
87+ url = self._parsed_url.clone(relpath)
88+ url.user = url.quoted_user = None
89+ url.password = url.quoted_password = None
90+ url.scheme = self._unqualified_scheme
91+ return str(url)
92+
93+ def external_url(self):
94+ """See breezy.transport.Transport.external_url."""
95+ # HTTP URL's are externally usable as long as they don't mention their
96+ # implementation qualifier
97+ url = self._parsed_url.clone()
98+ url.scheme = self._unqualified_scheme
99+ return str(url)
100+
101+ def is_readonly(self):
102+ """See Transport.is_readonly."""
103+ return True
104+
105+ def listable(self):
106+ """See Transport.listable."""
107+ return False
108+
109+ def stat(self, relpath):
110+ """Return the stat information for a file.
111+ """
112+ raise errors.TransportNotPossible('http does not support stat()')
113+
114+ def put_file(self, relpath, f, mode=None):
115+ """Copy the file-like object into the location.
116+
117+ :param relpath: Location to put the contents, relative to base.
118+ :param f: File-like object.
119+ """
120+ raise errors.TransportNotPossible('http PUT not supported')
121+
122+ def mkdir(self, relpath, mode=None):
123+ """Create a directory at the given path."""
124+ raise errors.TransportNotPossible('http does not support mkdir()')
125+
126+ def rmdir(self, relpath):
127+ """See Transport.rmdir."""
128+ raise errors.TransportNotPossible('http does not support rmdir()')
129+
130+ def append_file(self, relpath, f, mode=None):
131+ """Append the text in the file-like object into the final
132+ location.
133+ """
134+ raise errors.TransportNotPossible('http does not support append()')
135+
136+ def copy(self, rel_from, rel_to):
137+ """Copy the item at rel_from to the location at rel_to"""
138+ raise errors.TransportNotPossible('http does not support copy()')
139+
140+ def copy_to(self, relpaths, other, mode=None, pb=None):
141+ """Copy a set of entries from self into another Transport.
142+
143+ :param relpaths: A list/generator of entries to be copied.
144+
145+ TODO: if other is LocalTransport, is it possible to
146+ do better than put(get())?
147+ """
148+ # At this point HttpxTransport might be able to check and see if
149+ # the remote location is the same, and rather than download, and
150+ # then upload, it could just issue a remote copy_this command.
151+ if isinstance(other, HttpxTransport):
152+ raise errors.TransportNotPossible(
153+ 'http cannot be the target of copy_to()')
154+ else:
155+ return super(HttpxTransport, self).\
156+ copy_to(relpaths, other, mode=mode, pb=pb)
157+
158+ def move(self, rel_from, rel_to):
159+ """Move the item at rel_from to the location at rel_to"""
160+ raise errors.TransportNotPossible('http does not support move()')
161+
162+ def delete(self, relpath):
163+ """Delete the item at relpath"""
164+ raise errors.TransportNotPossible('http does not support delete()')
165+
166+ def has(self, relpath):
167+ """Does the target location exist?
168+ """
169+ abspath = self._remote_path(relpath)
170+ response = self.request('HEAD', abspath)
171+ if response.status_code not in (200, 404):
172+ raise errors.UnexpectedHttpStatus(abspath, response.status_code)
173+
174+ if response.status_code == 200: # "ok",
175+ return True
176+ else:
177+ return False
178+
179+ def _get(self, relpath):
180+ abspath = self._remote_path(relpath)
181+ response = self.request('GET', abspath)
182+ if response.status_code == 200:
183+ return response
184+ if response.status_code == 404:
185+ raise errors.NoSuchFile(relpath)
186+ raise errors.UnexpectedHttpStatus(abspath, response.status_code)
187+
188+ def get(self, relpath):
189+ return BytesIO(self._get(relpath).content)
190+
191+ def get_bytes(self, relpath):
192+ response = self._get(relpath)
193+ return response.content
194+
195+
196+def get_test_permutations():
197+ """Return the permutations to be used in testing."""
198+ from breezy.tests import (
199+ features,
200+ http_server,
201+ )
202+ permutations = [(HttpxTransport, http_server.HttpServer), ]
203+ if features.HTTPSServerFeature.available():
204+ from breezy.tests import (
205+ https_server,
206+ ssl_certs,
207+ )
208+
209+ class HTTPS_transport(HttpxTransport):
210+
211+ def __init__(self, base, _from_transport=None):
212+ super(HTTPS_transport, self).__init__(
213+ base, _from_transport=_from_transport,
214+ ca_certs=ssl_certs.build_path('ca.crt'))
215+
216+ permutations.append((HTTPS_transport,
217+ https_server.HTTPSServer))
218+ return permutations

Subscribers

People subscribed via source and target branches