Skip to content

Commit 10a514e

Browse files
committedDec 17, 2018
Merge branch 'release/1.10.2'
2 parents 815cb9e + 95321eb commit 10a514e

17 files changed

+527
-183
lines changed
 

‎.ci/jenkins/Jenkinsfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (env.BRANCH_NAME =~ /(^release.*)|(^master)/) {
1515
def slaves = ['Linux', 'Windows', 'Macos']
1616
def flavors = ["blocked_v2"]
1717
if(test_revisions){
18-
flavors = ["blocked_v2", "disabled_revisions", "enabled_revisions"]
18+
flavors = ["enabled_revisions", "disabled_revisions", "blocked_v2"]
1919
}
2020
def pyvers = ['py36', 'py27']
2121
def api_confs = ["v1"]

‎conans/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
SERVER_CAPABILITIES = [COMPLEX_SEARCH_CAPABILITY, REVISIONS] # Server is always with revisions
1919
DEFAULT_REVISION_V1 = "0"
2020

21-
__version__ = '1.10.1'
21+
__version__ = '1.10.2'

‎conans/client/rest/client_routes.py

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
from six.moves.urllib.parse import urlencode
2+
3+
from conans.errors import ConanException
4+
from conans.model.ref import ConanFileReference
5+
from conans.model.rest_routes import RestRoutes
6+
from conans.paths import CONAN_MANIFEST, CONANINFO
7+
8+
9+
class ClientBaseRouterBuilder(object):
10+
bad_package_revision = "It is needed to specify the recipe revision if you " \
11+
"specify a package revision"
12+
13+
def __init__(self, base_url):
14+
self.routes = RestRoutes(base_url)
15+
16+
def ping(self):
17+
return self.routes.ping
18+
19+
@staticmethod
20+
def format_ref(url, ref):
21+
url = url.format(name=ref.name, version=ref.version, username=ref.user,
22+
channel=ref.channel, revision=ref.revision)
23+
return url
24+
25+
@staticmethod
26+
def format_ref_path(url, ref, path):
27+
url = url.format(name=ref.name, version=ref.version, username=ref.user,
28+
channel=ref.channel, revision=ref.revision, path=path)
29+
return url
30+
31+
@staticmethod
32+
def format_pref(url, pref):
33+
ref = pref.conan
34+
url = url.format(name=ref.name, version=ref.version, username=ref.user,
35+
channel=ref.channel, revision=ref.revision, package_id=pref.package_id,
36+
p_revision=pref.revision)
37+
return url
38+
39+
@staticmethod
40+
def format_pref_path(url, pref, path):
41+
ref = pref.conan
42+
url = url.format(name=ref.name, version=ref.version, username=ref.user,
43+
channel=ref.channel, revision=ref.revision, package_id=pref.package_id,
44+
p_revision=pref.revision, path=path)
45+
return url
46+
47+
def for_recipe(self, ref):
48+
"""url for a recipe with or without revisions"""
49+
if not ref.revision:
50+
tmp = self.routes.recipe
51+
else:
52+
tmp = self.routes.recipe_revision
53+
return self.format_ref(tmp, ref)
54+
55+
def for_packages(self, ref):
56+
"""url for a recipe with or without revisions"""
57+
if not ref.revision:
58+
tmp = self.routes.packages
59+
else:
60+
tmp = self.routes.packages_revision
61+
return self.format_ref(tmp, ref)
62+
63+
def for_recipe_file(self, ref, path):
64+
"""url for a recipe file, with or without revisions"""
65+
if not ref.revision:
66+
tmp = self.routes.recipe_file
67+
else:
68+
tmp = self.routes.recipe_revision_file
69+
return self.format_ref_path(tmp, ref, path)
70+
71+
def for_recipe_files(self, ref):
72+
"""url for getting the recipe list"""
73+
if not ref.revision:
74+
tmp = self.routes.recipe_files
75+
else:
76+
tmp = self.routes.recipe_revision_files
77+
return self.format_ref(tmp, ref)
78+
79+
def for_package(self, pref):
80+
"""url for the package with or without revisions"""
81+
if not pref.conan.revision:
82+
if pref.revision:
83+
raise ConanException(self.bad_package_revision)
84+
tmp = self.routes.package
85+
elif not pref.revision:
86+
tmp = self.routes.package_recipe_revision
87+
else:
88+
tmp = self.routes.package_revision
89+
90+
return self.format_pref(tmp, pref)
91+
92+
def for_package_file(self, pref, path):
93+
"""url for getting a file from a package, with or without revisions"""
94+
if not pref.conan.revision:
95+
if pref.revision:
96+
raise ConanException(self.bad_package_revision)
97+
tmp = self.routes.package_file
98+
elif not pref.revision:
99+
tmp = self.routes.package_recipe_revision_file
100+
else:
101+
tmp = self.routes.package_revision_file
102+
103+
return self.format_pref_path(tmp, pref, path)
104+
105+
def for_package_files(self, pref):
106+
"""url for getting the recipe list"""
107+
if not pref.conan.revision:
108+
if pref.revision:
109+
raise ConanException(self.bad_package_revision)
110+
tmp = self.routes.package_files
111+
elif not pref.revision:
112+
tmp = self.routes.package_recipe_revision_files
113+
else:
114+
tmp = self.routes.package_revision_files
115+
116+
return self.format_pref(tmp, pref)
117+
118+
119+
class ClientSearchRouterBuilder(ClientBaseRouterBuilder):
120+
"""Search urls shared between v1 and v2"""
121+
122+
def __init__(self, base_url):
123+
super(ClientSearchRouterBuilder, self).__init__(base_url + "/conans")
124+
125+
def search(self, pattern, ignorecase):
126+
"""URL search recipes"""
127+
query = ''
128+
if pattern:
129+
if isinstance(pattern, ConanFileReference):
130+
pattern = pattern.full_repr()
131+
params = {"q": pattern}
132+
if not ignorecase:
133+
params["ignorecase"] = "False"
134+
query = "?%s" % urlencode(params)
135+
return "%s%s" % (self.routes.common_search, query)
136+
137+
def search_packages(self, ref, query=None):
138+
"""URL search packages for a recipe"""
139+
route = self.routes.common_search_packages_revision \
140+
if ref.revision else self.routes.common_search_packages
141+
url = self.format_ref(route, ref)
142+
if query:
143+
url += "?%s" % urlencode({"q": query})
144+
return url
145+
146+
147+
class ClientUsersRouterBuilder(ClientBaseRouterBuilder):
148+
"""Builds urls for users endpoint (shared v1 and v2)"""
149+
def __init__(self, base_url):
150+
super(ClientUsersRouterBuilder, self).__init__(base_url + "/users")
151+
152+
def common_authenticate(self):
153+
return self.routes.common_authenticate
154+
155+
def common_check_credentials(self):
156+
return self.routes.common_check_credentials
157+
158+
159+
class ClientV1ConanRouterBuilder(ClientBaseRouterBuilder):
160+
"""Builds urls for v1"""
161+
162+
def __init__(self, base_url):
163+
super(ClientV1ConanRouterBuilder, self).__init__(base_url + "/conans")
164+
165+
def remove_recipe(self, ref):
166+
"""Remove recipe"""
167+
return self.for_recipe(ref.copy_clear_rev())
168+
169+
def remove_recipe_files(self, ref):
170+
"""Removes files from the recipe"""
171+
return self.format_ref(self.routes.v1_remove_recipe_files, ref.copy_clear_rev())
172+
173+
def remove_packages(self, ref):
174+
"""Remove files from a package"""
175+
return self.format_ref(self.routes.v1_remove_packages, ref.copy_clear_rev())
176+
177+
def recipe_snapshot(self, ref):
178+
"""get recipe manifest url"""
179+
return self.for_recipe(ref.copy_clear_rev())
180+
181+
def package_snapshot(self, pref):
182+
"""get recipe manifest url"""
183+
return self.for_package(pref.copy_clear_rev())
184+
185+
def recipe_manifest(self, ref):
186+
"""get recipe manifest url"""
187+
return self.format_ref(self.routes.v1_recipe_digest, ref.copy_clear_rev())
188+
189+
def package_manifest(self, pref):
190+
"""get manifest url"""
191+
return self.format_pref(self.routes.v1_package_digest, pref.copy_clear_rev())
192+
193+
def recipe_download_urls(self, ref):
194+
""" urls to download the recipe"""
195+
return self.format_ref(self.routes.v1_recipe_download_urls, ref.copy_clear_rev())
196+
197+
def package_download_urls(self, pref):
198+
""" urls to download the package"""
199+
return self.format_pref(self.routes.v1_package_download_urls, pref.copy_clear_rev())
200+
201+
def recipe_upload_urls(self, ref):
202+
""" urls to upload the recipe"""
203+
return self.format_ref(self.routes.v1_recipe_upload_urls, ref.copy_clear_rev())
204+
205+
def package_upload_urls(self, pref):
206+
""" urls to upload the package"""
207+
return self.format_pref(self.routes.v1_package_upload_urls, pref.copy_clear_rev())
208+
209+
210+
class ClientV2ConanRouterBuilder(ClientBaseRouterBuilder):
211+
"""Builds urls for v2"""
212+
213+
def __init__(self, base_url):
214+
super(ClientV2ConanRouterBuilder, self).__init__(base_url + "/conans")
215+
216+
def recipe_file(self, ref, path):
217+
"""Recipe file url"""
218+
return self.for_recipe_file(ref, path)
219+
220+
def package_file(self, pref, path):
221+
"""Package file url"""
222+
return self.for_package_file(pref, path)
223+
224+
def remove_recipe(self, ref):
225+
"""Remove recipe url"""
226+
return self.for_recipe(ref)
227+
228+
def remove_package(self, pref):
229+
"""Remove package url"""
230+
return self.for_package(pref)
231+
232+
def remove_all_packages(self, ref):
233+
"""Remove package url"""
234+
return self.for_packages(ref)
235+
236+
def recipe_manifest(self, ref):
237+
"""Get the url for getting a conanmanifest.txt from a recipe"""
238+
return self.for_recipe_file(ref, CONAN_MANIFEST)
239+
240+
def package_manifest(self, pref):
241+
"""Get the url for getting a conanmanifest.txt from a package"""
242+
return self.for_package_file(pref, CONAN_MANIFEST)
243+
244+
def package_info(self, pref):
245+
"""Get the url for getting a conaninfo.txt from a package"""
246+
return self.for_package_file(pref, CONANINFO)
247+
248+
def recipe_snapshot(self, ref):
249+
"""get recipe manifest url"""
250+
return self.for_recipe_files(ref)
251+
252+
def package_snapshot(self, pref):
253+
"""get recipe manifest url"""
254+
return self.for_package_files(pref)

‎conans/client/rest/rest_client_common.py

+25-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import json
2+
23
import time
4+
from requests.auth import AuthBase, HTTPBasicAuth
5+
36
from conans import COMPLEX_SEARCH_CAPABILITY, DEFAULT_REVISION_V1
47
from conans.client.cmd.uploader import UPLOAD_POLICY_NO_OVERWRITE, \
58
UPLOAD_POLICY_NO_OVERWRITE_RECIPE, UPLOAD_POLICY_FORCE
9+
from conans.client.rest.client_routes import ClientUsersRouterBuilder, \
10+
ClientSearchRouterBuilder, ClientBaseRouterBuilder
611
from conans.errors import (EXCEPTION_CODE_MAPPING, NotFoundException, ConanException,
712
AuthenticationException)
813
from conans.model.manifest import FileTreeManifest
@@ -11,8 +16,6 @@
1116
from conans.util.env_reader import get_env
1217
from conans.util.files import decode_text, load
1318
from conans.util.log import logger
14-
from requests.auth import AuthBase, HTTPBasicAuth
15-
from six.moves.urllib.parse import urlencode
1619

1720

1821
class JWTAuth(AuthBase):
@@ -77,11 +80,23 @@ def __init__(self, remote_url, token, custom_headers, output, requester, verify_
7780
def auth(self):
7881
return JWTAuth(self.token)
7982

83+
@property
84+
def users_router(self):
85+
return ClientUsersRouterBuilder(self.remote_api_url)
86+
87+
@property
88+
def search_router(self):
89+
return ClientSearchRouterBuilder(self.remote_api_url)
90+
91+
@property
92+
def base_router(self):
93+
return ClientBaseRouterBuilder(self.remote_api_url)
94+
8095
@handle_return_deserializer()
8196
def authenticate(self, user, password):
8297
"""Sends user + password to get a token"""
8398
auth = HTTPBasicAuth(user, password)
84-
url = "%s/users/authenticate" % self.remote_api_url
99+
url = self.users_router.common_authenticate()
85100
ret = self.requester.get(url, auth=auth, headers=self.custom_headers,
86101
verify=self.verify_ssl)
87102
if ret.status_code == 401:
@@ -96,14 +111,14 @@ def authenticate(self, user, password):
96111
def check_credentials(self):
97112
"""If token is not valid will raise AuthenticationException.
98113
User will be asked for new user/pass"""
99-
url = "%s/users/check_credentials" % self.remote_api_url
114+
url = self.users_router.common_check_credentials()
100115
ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
101116
verify=self.verify_ssl)
102117
return ret
103118

104119
def server_info(self):
105120
"""Get information about the server: status, version, type and capabilities"""
106-
url = "%s/ping" % self.remote_api_url
121+
url = self.base_router.ping()
107122
ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
108123
verify=self.verify_ssl)
109124
if ret.status_code == 404:
@@ -235,23 +250,14 @@ def search(self, pattern=None, ignorecase=True):
235250
"""
236251
the_files: dict with relative_path: content
237252
"""
238-
query = ''
239-
if pattern:
240-
if isinstance(pattern, ConanFileReference):
241-
pattern = pattern.full_repr()
242-
params = {"q": pattern}
243-
if not ignorecase:
244-
params["ignorecase"] = "False"
245-
query = "?%s" % urlencode(params)
246-
247-
url = "%s/conans/search%s" % (self.remote_api_url, query)
253+
url = self.search_router.search(pattern, ignorecase)
248254
response = self.get_json(url)["results"]
249255
return [ConanFileReference.loads(ref) for ref in response]
250256

251257
def search_packages(self, reference, query):
252-
url = "%s/search?" % self._recipe_url(reference)
253258

254259
if not query:
260+
url = self.search_router.search_packages(reference)
255261
package_infos = self.get_json(url)
256262
return package_infos
257263

@@ -262,18 +268,19 @@ def search_packages(self, reference, query):
262268
capabilities = []
263269

264270
if COMPLEX_SEARCH_CAPABILITY in capabilities:
265-
url += urlencode({"q": query})
271+
url = self.search_router.search_packages(reference, query)
266272
package_infos = self.get_json(url)
267273
return package_infos
268274
else:
275+
url = self.search_router.search_packages(reference)
269276
package_infos = self.get_json(url)
270277
return filter_packages(query, package_infos)
271278

272279
@handle_return_deserializer()
273280
def remove_conanfile(self, conan_reference):
274281
""" Remove a recipe and packages """
275282
self.check_credentials()
276-
url = self._recipe_url(conan_reference)
283+
url = self.conans_router.remove_recipe(conan_reference)
277284
response = self.requester.delete(url,
278285
auth=self.auth,
279286
headers=self.custom_headers,

‎conans/client/rest/rest_client_v1.py

+19-23
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from conans import DEFAULT_REVISION_V1
77
from conans.client.remote_manager import check_compressed_files
8+
from conans.client.rest.client_routes import ClientV1ConanRouterBuilder
89
from conans.client.rest.rest_client_common import RestCommonMethods, handle_return_deserializer
910
from conans.client.rest.uploader_downloader import Downloader, Uploader
1011
from conans.errors import NotFoundException, ConanException
@@ -32,6 +33,10 @@ class RestV1Methods(RestCommonMethods):
3233
def remote_api_url(self):
3334
return "%s/v1" % self.remote_url.rstrip("/")
3435

36+
@property
37+
def conans_router(self):
38+
return ClientV1ConanRouterBuilder(self.remote_api_url)
39+
3540
def _download_files(self, file_urls, quiet=False):
3641
"""
3742
:param: file_urls is a dict with {filename: url}
@@ -67,7 +72,7 @@ def get_conan_manifest(self, conan_reference):
6772
"""Gets a FileTreeManifest from conans"""
6873

6974
# Obtain the URLs
70-
url = "%s/digest" % self._recipe_url(conan_reference)
75+
url = self.conans_router.recipe_manifest(conan_reference)
7176
urls = self._get_file_to_url_dict(url)
7277

7378
# Get the digest
@@ -80,7 +85,7 @@ def get_package_manifest(self, package_reference):
8085
"""Gets a FileTreeManifest from a package"""
8186

8287
# Obtain the URLs
83-
url = "%s/digest" % self._package_url(package_reference)
88+
url = self.conans_router.package_manifest(package_reference)
8489
urls = self._get_file_to_url_dict(url)
8590

8691
# Get the digest
@@ -92,7 +97,7 @@ def get_package_manifest(self, package_reference):
9297
def get_package_info(self, package_reference):
9398
"""Gets a ConanInfo file from a package"""
9499

95-
url = "%s/download_urls" % self._package_url(package_reference)
100+
url = self.conans_router.package_download_urls(package_reference)
96101
urls = self._get_file_to_url_dict(url)
97102
if not urls:
98103
raise NotFoundException("Package not found!")
@@ -114,15 +119,15 @@ def _get_file_to_url_dict(self, url, data=None):
114119

115120
def _upload_recipe(self, conan_reference, files_to_upload, retry, retry_wait):
116121
# Get the upload urls and then upload files
117-
url = "%s/upload_urls" % self._recipe_url(conan_reference)
122+
url = self.conans_router.recipe_upload_urls(conan_reference)
118123
filesizes = {filename.replace("\\", "/"): os.stat(abs_path).st_size
119124
for filename, abs_path in files_to_upload.items()}
120125
urls = self._get_file_to_url_dict(url, data=filesizes)
121126
self._upload_files(urls, files_to_upload, self._output, retry, retry_wait)
122127

123128
def _upload_package(self, package_reference, files_to_upload, retry, retry_wait):
124129
# Get the upload urls and then upload files
125-
url = "%s/upload_urls" % self._package_url(package_reference)
130+
url = self.conans_router.package_upload_urls(package_reference)
126131
filesizes = {filename: os.stat(abs_path).st_size for filename,
127132
abs_path in files_to_upload.items()}
128133
self._output.rewrite_line("Requesting upload urls...")
@@ -201,7 +206,7 @@ def get_recipe_sources(self, conan_reference, dest_folder):
201206
def _get_recipe_urls(self, conan_reference):
202207
"""Gets a dict of filename:contents from conans"""
203208
# Get the conanfile snapshot first
204-
url = "%s/download_urls" % self._recipe_url(conan_reference)
209+
url = self.conans_router.recipe_download_urls(conan_reference)
205210
urls = self._get_file_to_url_dict(url)
206211
return urls
207212

@@ -215,7 +220,7 @@ def get_package(self, package_reference, dest_folder):
215220

216221
def _get_package_urls(self, package_reference):
217222
"""Gets a dict of filename:contents from package"""
218-
url = "%s/download_urls" % self._package_url(package_reference)
223+
url = self.conans_router.package_download_urls(package_reference)
219224
urls = self._get_file_to_url_dict(url)
220225
if not urls:
221226
raise NotFoundException("Package not found!")
@@ -225,11 +230,11 @@ def _get_package_urls(self, package_reference):
225230
def get_path(self, conan_reference, package_id, path):
226231
"""Gets a file content or a directory list"""
227232

228-
tmp = "%s/download_urls"
229233
if not package_id:
230-
url = tmp % self._recipe_url(conan_reference)
234+
url = self.conans_router.recipe_download_urls(conan_reference)
231235
else:
232-
url = tmp % self._package_url(PackageReference(conan_reference, package_id))
236+
pref = PackageReference(conan_reference, package_id)
237+
url = self.conans_router.package_download_urls(pref)
233238
try:
234239
urls = self._get_file_to_url_dict(url)
235240
except NotFoundException:
@@ -274,38 +279,29 @@ def _get_snapshot(self, url):
274279
return snapshot
275280

276281
def _get_recipe_snapshot(self, reference):
277-
url = self._recipe_url(reference)
282+
url = self.conans_router.recipe_snapshot(reference)
278283
snap = self._get_snapshot(url)
279284
rev_time = None
280285
return snap, reference.copy_with_rev(DEFAULT_REVISION_V1), rev_time
281286

282287
def _get_package_snapshot(self, package_reference):
283-
url = self._package_url(package_reference)
288+
url = self.conans_router.package_snapshot(package_reference)
284289
snap = self._get_snapshot(url)
285290
rev_time = None
286291
ret_ref = package_reference.copy_with_revs(DEFAULT_REVISION_V1, DEFAULT_REVISION_V1)
287292
return snap, ret_ref, rev_time
288293

289-
def _recipe_url(self, conan_reference):
290-
return "%s/conans/%s" % (self.remote_api_url, conan_reference.dir_repr())
291-
292-
def _package_url(self, p_reference):
293-
url = self._recipe_url(p_reference.conan)
294-
url += "/packages/%s" % p_reference.package_id
295-
return url
296-
297294
@handle_return_deserializer()
298295
def _remove_conanfile_files(self, conan_reference, files):
299-
""" Remove recipe files """
300296
self.check_credentials()
301297
payload = {"files": [filename.replace("\\", "/") for filename in files]}
302-
url = self._recipe_url(conan_reference) + "/remove_files"
298+
url = self.conans_router.remove_recipe_files(conan_reference)
303299
return self._post_json(url, payload)
304300

305301
@handle_return_deserializer()
306302
def remove_packages(self, conan_reference, package_ids=None):
307303
""" Remove any packages specified by package_ids"""
308304
self.check_credentials()
309305
payload = {"package_ids": package_ids}
310-
url = self._recipe_url(conan_reference) + "/packages/delete"
306+
url = self.conans_router.remove_packages(conan_reference)
311307
return self._post_json(url, payload)

‎conans/client/rest/rest_client_v2.py

+45-52
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import os
2+
23
import time
34

45
from conans.client.remote_manager import check_compressed_files
5-
from conans.client.rest.rest_client_common import RestCommonMethods, handle_return_deserializer
6+
from conans.client.rest.client_routes import ClientV2ConanRouterBuilder
7+
from conans.client.rest.rest_client_common import RestCommonMethods
68
from conans.client.rest.uploader_downloader import Downloader, Uploader
79
from conans.errors import NotFoundException, ConanException
810
from conans.model.info import ConanInfo
911
from conans.model.manifest import FileTreeManifest
1012
from conans.model.ref import PackageReference, ConanFileReference
11-
from conans.paths import CONAN_MANIFEST, CONANINFO, EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, \
13+
from conans.paths import EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, \
1214
PACKAGE_TGZ_NAME
13-
from conans.util.env_reader import get_env
1415
from conans.util.files import decode_text
1516
from conans.util.log import logger
1617

@@ -28,6 +29,10 @@ def __init__(self, remote_url, token, custom_headers, output, requester, verify_
2829
def remote_api_url(self):
2930
return "%s/v2" % self.remote_url.rstrip("/")
3031

32+
@property
33+
def conans_router(self):
34+
return ClientV2ConanRouterBuilder(self.remote_api_url)
35+
3136
def _get_file_list_json(self, url):
3237
data = self.get_json(url)
3338
# Discarding (.keys()) still empty metadata for files
@@ -51,53 +56,53 @@ def _get_snapshot(self, url, reference):
5156
return files_list, reference, rev_time
5257

5358
def _get_recipe_snapshot(self, reference):
54-
url = self._recipe_url(reference)
59+
url = self.conans_router.recipe_snapshot(reference)
5560
repr_ref = reference.full_repr()
5661
snap, reference, rev_time = self._get_snapshot(url, repr_ref)
5762
reference = ConanFileReference.loads(reference)
5863
return snap, reference, rev_time
5964

6065
def _get_package_snapshot(self, p_ref):
61-
url = self._package_url(p_ref)
66+
url = self.conans_router.package_snapshot(p_ref)
6267
repr_ref = p_ref.full_repr()
6368
snap, p_reference, rev_time = self._get_snapshot(url, repr_ref)
6469

6570
reference = PackageReference.loads(p_reference)
6671
return snap, reference, rev_time
6772

6873
def get_conan_manifest(self, conan_reference):
69-
url = "%s/%s" % (self._recipe_url(conan_reference), CONAN_MANIFEST)
74+
url = self.conans_router.recipe_manifest(conan_reference)
7075
content = self._get_remote_file_contents(url)
7176
return FileTreeManifest.loads(decode_text(content))
7277

7378
def get_package_manifest(self, package_reference):
74-
url = "%s/%s" % (self._package_url(package_reference), CONAN_MANIFEST)
79+
url = self.conans_router.package_manifest(package_reference)
7580
content = self._get_remote_file_contents(url)
7681
return FileTreeManifest.loads(decode_text(content))
7782

7883
def get_package_info(self, package_reference):
79-
url = "%s/%s" % (self._package_url(package_reference), CONANINFO)
84+
url = self.conans_router.package_info(package_reference)
8085
content = self._get_remote_file_contents(url)
8186
return ConanInfo.loads(decode_text(content))
8287

8388
def get_recipe(self, conan_reference, dest_folder):
84-
url = self._recipe_url(conan_reference)
89+
url = self.conans_router.recipe_snapshot(conan_reference)
8590
data = self._get_file_list_json(url)
8691
files = data["files"]
8792
rev_time = data["time"]
8893
check_compressed_files(EXPORT_TGZ_NAME, files)
89-
reference = ConanFileReference.loads(data["reference"])
94+
new_ref = ConanFileReference.loads(data["reference"])
9095
if EXPORT_SOURCES_TGZ_NAME in files:
9196
files.remove(EXPORT_SOURCES_TGZ_NAME)
9297

9398
# If we didn't indicated reference, server got the latest, use absolute now, it's safer
94-
url = self._recipe_url(reference)
95-
self._download_and_save_files(url, dest_folder, files)
99+
urls = {fn: self.conans_router.recipe_file(conan_reference, fn) for fn in files}
100+
self._download_and_save_files(urls, dest_folder, files)
96101
ret = {fn: os.path.join(dest_folder, fn) for fn in files}
97-
return ret, reference, rev_time
102+
return ret, new_ref, rev_time
98103

99104
def get_recipe_sources(self, conan_reference, dest_folder):
100-
url = self._recipe_url(conan_reference)
105+
url = self.conans_router.recipe_snapshot(conan_reference)
101106
data = self._get_file_list_json(url)
102107
files = data["files"]
103108
check_compressed_files(EXPORT_SOURCES_TGZ_NAME, files)
@@ -106,31 +111,33 @@ def get_recipe_sources(self, conan_reference, dest_folder):
106111
files = [EXPORT_SOURCES_TGZ_NAME, ]
107112

108113
# If we didn't indicated reference, server got the latest, use absolute now, it's safer
109-
url = self._recipe_url(ConanFileReference.loads(data["reference"]))
110-
self._download_and_save_files(url, dest_folder, files)
114+
new_ref = ConanFileReference.loads(data["reference"])
115+
urls = {fn: self.conans_router.recipe_file(new_ref, fn) for fn in files}
116+
self._download_and_save_files(urls, dest_folder, files)
111117
ret = {fn: os.path.join(dest_folder, fn) for fn in files}
112118
return ret
113119

114120
def get_package(self, package_reference, dest_folder):
115-
url = self._package_url(package_reference)
121+
url = self.conans_router.package_snapshot(package_reference)
116122
data = self._get_file_list_json(url)
117123
files = data["files"]
118124
rev_time = data["time"]
119125
check_compressed_files(PACKAGE_TGZ_NAME, files)
120126
new_reference = PackageReference.loads(data["reference"])
121127
# If we didn't indicated reference, server got the latest, use absolute now, it's safer
122-
url = self._package_url(PackageReference.loads(data["reference"]))
123-
self._download_and_save_files(url, dest_folder, files)
128+
new_pref = PackageReference.loads(data["reference"])
129+
urls = {fn: self.conans_router.package_file(new_pref, fn) for fn in files}
130+
self._download_and_save_files(urls, dest_folder, files)
124131
ret = {fn: os.path.join(dest_folder, fn) for fn in files}
125132
return ret, new_reference, rev_time
126133

127134
def get_path(self, conan_reference, package_id, path):
128135

129136
if not package_id:
130-
url = self._recipe_url(conan_reference)
137+
url = self.conans_router.recipe_snapshot(conan_reference)
131138
else:
132139
package_ref = PackageReference(conan_reference, package_id)
133-
url = self._package_url(package_ref)
140+
url = self.conans_router.package_snapshot(package_ref)
134141

135142
try:
136143
files = self._get_file_list_json(url)
@@ -159,28 +166,34 @@ def is_dir(the_path):
159166
ret.append(tmp)
160167
return sorted(ret)
161168
else:
162-
url += "/%s" % path
169+
if not package_id:
170+
url = self.conans_router.recipe_file(conan_reference, path)
171+
else:
172+
package_ref = PackageReference(conan_reference, package_id)
173+
url = self.conans_router.package_file(package_ref, path)
174+
163175
content = self._get_remote_file_contents(url)
164176
return decode_text(content)
165177

166178
def _upload_recipe(self, conan_reference, files_to_upload, retry, retry_wait):
167179
# Direct upload the recipe
168-
url = self._recipe_url(conan_reference)
169-
self._upload_files(files_to_upload, url, retry, retry_wait)
180+
urls = {fn: self.conans_router.recipe_file(conan_reference, fn) for fn in files_to_upload}
181+
self._upload_files(files_to_upload, urls, retry, retry_wait)
170182

171183
def _upload_package(self, package_reference, files_to_upload, retry, retry_wait):
172-
url = self._package_url(package_reference)
173-
self._upload_files(files_to_upload, url, retry, retry_wait)
184+
urls = {fn: self.conans_router.package_file(package_reference, fn)
185+
for fn in files_to_upload}
186+
self._upload_files(files_to_upload, urls, retry, retry_wait)
174187

175-
def _upload_files(self, files, base_url, retry, retry_wait):
188+
def _upload_files(self, files, urls, retry, retry_wait):
176189
t1 = time.time()
177190
failed = []
178191
uploader = Uploader(self.requester, self._output, self.verify_ssl)
179192
# Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
180193
# can be < conanfile, conaninfo, and sent always the last, so smaller files go first
181194
for filename in sorted(files, reverse=True):
182195
self._output.rewrite_line("Uploading %s" % filename)
183-
resource_url = "%s/%s" % (base_url, filename)
196+
resource_url = urls[filename]
184197
try:
185198
response = uploader.upload(resource_url, files[filename], auth=self.auth,
186199
dedup=self._checksum_deploy, retry=retry,
@@ -203,37 +216,17 @@ def _upload_files(self, files, base_url, retry, retry_wait):
203216
else:
204217
logger.debug("\nAll uploaded! Total time: %s\n" % str(time.time() - t1))
205218

206-
def _download_and_save_files(self, base_url, dest_folder, files):
219+
def _download_and_save_files(self, urls, dest_folder, files):
207220
downloader = Downloader(self.requester, self._output, self.verify_ssl)
208221
# Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
209222
# can be < conanfile, conaninfo, and sent always the last, so smaller files go first
210223
for filename in sorted(files, reverse=True):
211224
if self._output:
212225
self._output.writeln("Downloading %s" % filename)
213-
resource_url = "%s/%s" % (base_url, filename)
226+
resource_url = urls[filename]
214227
abs_path = os.path.join(dest_folder, filename)
215228
downloader.download(resource_url, abs_path, auth=self.auth)
216229

217-
def _recipe_url(self, conan_reference):
218-
219-
url = "%s/conans/%s" % (self.remote_api_url, conan_reference.dir_repr())
220-
221-
if conan_reference.revision:
222-
url += "/revisions/%s" % conan_reference.revision
223-
return url
224-
225-
def _package_url(self, p_reference):
226-
if not p_reference.conan.revision and p_reference.revision:
227-
raise ConanException("It is needed to specify the recipe revision if you "
228-
"specify a package revision")
229-
230-
url = self._recipe_url(p_reference.conan)
231-
url += "/packages/%s" % p_reference.package_id
232-
if p_reference.revision:
233-
assert(p_reference.revision is not None)
234-
url += "/revisions/%s" % p_reference.revision
235-
return url
236-
237230
def _remove_conanfile_files(self, conan_reference, files):
238231
# V2 === revisions, do not remove files, it will create a new revision if the files changed
239232
return
@@ -242,12 +235,12 @@ def remove_packages(self, conan_reference, package_ids=None):
242235
""" Remove any packages specified by package_ids"""
243236
self.check_credentials()
244237
if not package_ids:
245-
url = self._recipe_url(conan_reference) + "/packages"
238+
url = self.conans_router.remove_all_packages(conan_reference)
246239
self.requester.delete(url, auth=self.auth, headers=self.custom_headers,
247240
verify=self.verify_ssl)
248241
return
249242
for pid in package_ids:
250243
pref = PackageReference(conan_reference, pid)
251-
url = self._package_url(pref)
244+
url = self.conans_router.remove_package(pref)
252245
self.requester.delete(url, auth=self.auth, headers=self.custom_headers,
253246
verify=self.verify_ssl)

‎conans/model/rest_routes.py

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
class RestRoutes(object):
3+
4+
def __init__(self, base_url):
5+
self.base_url = base_url
6+
7+
@property
8+
def recipe(self):
9+
return '%s/{name}/{version}/{username}/{channel}' % self.base_url
10+
11+
@property
12+
def recipe_files(self):
13+
return '%s/files' % self.recipe
14+
15+
@property
16+
def recipe_revision(self):
17+
return '%s/revisions/{revision}' % self.recipe
18+
19+
@property
20+
def recipe_revision_files(self):
21+
return '%s/files' % self.recipe_revision
22+
23+
@property
24+
def recipe_revisions(self):
25+
return '%s/revisions' % self.recipe
26+
27+
@property
28+
def recipe_file(self):
29+
return '%s/files/{path}' % self.recipe
30+
31+
@property
32+
def recipe_revision_file(self):
33+
return '%s/files/{path}' % self.recipe_revision
34+
35+
@property
36+
def packages(self):
37+
return '%s/packages' % self.recipe
38+
39+
@property
40+
def packages_revision(self):
41+
return '%s/packages' % self.recipe_revision
42+
43+
@property
44+
def package(self):
45+
return '%s/{package_id}' % self.packages
46+
47+
@property
48+
def package_files(self):
49+
return '%s/files' % self.package
50+
51+
@property
52+
def package_recipe_revision(self):
53+
"""Route for a package specifying the recipe revision but not the package revision"""
54+
return '%s/{package_id}' % self.packages_revision
55+
56+
@property
57+
def package_recipe_revision_files(self):
58+
return '%s/files' % self.package_recipe_revision
59+
60+
@property
61+
def package_revisions(self):
62+
return '%s/revisions' % self.package_recipe_revision
63+
64+
@property
65+
def package_revision(self):
66+
return '%s/{p_revision}' % self.package_revisions
67+
68+
@property
69+
def package_revision_files(self):
70+
return '%s/files' % self.package_revision
71+
72+
@property
73+
def package_file(self):
74+
return '%s/files/{path}' % self.package
75+
76+
@property
77+
def package_revision_file(self):
78+
return '%s/files/{path}' % self.package_revision
79+
80+
@property
81+
def package_recipe_revision_file(self):
82+
return '%s/files/{path}' % self.package_recipe_revision
83+
84+
# ONLY V1
85+
@property
86+
def v1_recipe_digest(self):
87+
return "%s/digest" % self.recipe
88+
89+
@property
90+
def v1_package_digest(self):
91+
return "%s/digest" % self.package
92+
93+
@property
94+
def v1_recipe_download_urls(self):
95+
return "%s/download_urls" % self.recipe
96+
97+
@property
98+
def v1_package_download_urls(self):
99+
return "%s/download_urls" % self.package
100+
101+
@property
102+
def v1_recipe_upload_urls(self):
103+
return "%s/upload_urls" % self.recipe
104+
105+
@property
106+
def v1_package_upload_urls(self):
107+
return "%s/upload_urls" % self.package
108+
109+
@property
110+
def v1_remove_recipe_files(self):
111+
return "%s/remove_files" % self.recipe
112+
113+
@property
114+
def v1_remove_packages(self):
115+
return "%s/packages/delete" % self.recipe
116+
117+
# COMMON URLS
118+
@property
119+
def ping(self):
120+
return "%s/ping" % self.base_url
121+
122+
@property
123+
def common_search(self):
124+
return "%s/search" % self.base_url
125+
126+
@property
127+
def common_search_packages(self):
128+
return "%s/search" % self.recipe
129+
130+
@property
131+
def common_search_packages_revision(self):
132+
return "%s/search" % self.recipe_revision
133+
134+
@property
135+
def common_authenticate(self):
136+
return "%s/authenticate" % self.base_url
137+
138+
@property
139+
def common_check_credentials(self):
140+
return "%s/check_credentials" % self.base_url

‎conans/server/rest/bottle_routes.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from conans.model.rest_routes import RestRoutes
2+
3+
4+
class BottleRoutes(RestRoutes):
5+
6+
def __getattribute__(self, item):
7+
tmp = super(BottleRoutes, self).__getattribute__(item)
8+
return tmp.replace("{path}", "<the_path:path>").replace("{", "<").replace("}", ">")

‎conans/server/rest/controllers/routes.py

-55
This file was deleted.

‎conans/server/rest/controllers/search_controller.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from bottle import request
22

33
from conans.model.ref import ConanFileReference
4+
from conans.server.rest.bottle_routes import BottleRoutes
45
from conans.server.rest.controllers.controller import Controller
5-
from conans.server.rest.controllers.routes import Router
66
from conans.server.service.service import SearchService
77

88

@@ -12,7 +12,7 @@ class SearchController(Controller):
1212
"""
1313
def attach_to(self, app):
1414

15-
r = Router(self.route)
15+
r = BottleRoutes(self.route)
1616

1717
@app.route('%s/search' % r.base_url, method=["GET"])
1818
def search(auth_user):

‎conans/server/rest/controllers/v1/conan_controller.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from conans.model.ref import ConanFileReference, PackageReference
88
from conans.paths import CONAN_MANIFEST
99
from conans import DEFAULT_REVISION_V1
10+
from conans.server.rest.bottle_routes import BottleRoutes
1011
from conans.server.rest.controllers.controller import Controller
11-
from conans.server.rest.controllers.routes import Router
1212
from conans.server.service.service import ConanService
1313

1414

@@ -18,9 +18,9 @@ class ConanController(Controller):
1818
"""
1919
def attach_to(self, app):
2020

21-
r = Router(self.route)
21+
r = BottleRoutes(self.route)
2222

23-
@app.route("%s/digest" % r.recipe, method=["GET"])
23+
@app.route(r.v1_recipe_digest, method=["GET"])
2424
def get_conan_manifest_url(name, version, username, channel, auth_user):
2525
"""
2626
Get a dict with all files and the download url
@@ -32,7 +32,7 @@ def get_conan_manifest_url(name, version, username, channel, auth_user):
3232
raise NotFoundException("No digest found")
3333
return urls
3434

35-
@app.route("%s/digest" % r.package, method=["GET"])
35+
@app.route(r.v1_package_digest, method=["GET"])
3636
def get_package_manifest_url(name, version, username, channel, package_id, auth_user):
3737
"""
3838
Get a dict with all files and the download url
@@ -72,7 +72,7 @@ def get_package_snapshot(name, version, username, channel, package_id, auth_user
7272
for filename, the_md5 in snapshot.items()}
7373
return snapshot_norm
7474

75-
@app.route("%s/download_urls" % r.recipe, method=["GET"])
75+
@app.route(r.v1_recipe_download_urls, method=["GET"])
7676
def get_conanfile_download_urls(name, version, username, channel, auth_user):
7777
"""
7878
Get a dict with all files and the download url
@@ -83,7 +83,7 @@ def get_conanfile_download_urls(name, version, username, channel, auth_user):
8383
urls_norm = {filename.replace("\\", "/"): url for filename, url in urls.items()}
8484
return urls_norm
8585

86-
@app.route('%s/download_urls' % r.package, method=["GET"])
86+
@app.route(r.v1_package_download_urls, method=["GET"])
8787
def get_package_download_urls(name, version, username, channel, package_id,
8888
auth_user):
8989
"""
@@ -96,7 +96,7 @@ def get_package_download_urls(name, version, username, channel, package_id,
9696
urls_norm = {filename.replace("\\", "/"): url for filename, url in urls.items()}
9797
return urls_norm
9898

99-
@app.route("%s/upload_urls" % r.recipe, method=["POST"])
99+
@app.route(r.v1_recipe_upload_urls, method=["POST"])
100100
def get_conanfile_upload_urls(name, version, username, channel, auth_user):
101101
"""
102102
Get a dict with all files and the upload url
@@ -110,7 +110,7 @@ def get_conanfile_upload_urls(name, version, username, channel, auth_user):
110110
app.server_store.update_last_revision(reference)
111111
return urls_norm
112112

113-
@app.route('%s/upload_urls' % r.package, method=["POST"])
113+
@app.route(r.v1_package_upload_urls, method=["POST"])
114114
def get_package_upload_urls(name, version, username, channel, package_id, auth_user):
115115
"""
116116
Get a dict with all files and the upload url

‎conans/server/rest/controllers/v1/delete_controller.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
from conans.model.ref import ConanFileReference
88
from conans import DEFAULT_REVISION_V1
9+
from conans.server.rest.bottle_routes import BottleRoutes
910
from conans.server.rest.controllers.controller import Controller
10-
from conans.server.rest.controllers.routes import Router
1111
from conans.server.service.service import ConanService
1212

1313

@@ -17,7 +17,7 @@ class DeleteController(Controller):
1717
"""
1818
def attach_to(self, app):
1919

20-
r = Router(self.route)
20+
r = BottleRoutes(self.route)
2121

2222
@app.route(r.recipe, method="DELETE")
2323
def remove_recipe(name, version, username, channel, auth_user):

‎conans/server/rest/controllers/v1/search_controller.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from bottle import request
22

33
from conans.model.ref import ConanFileReference
4+
from conans.server.rest.bottle_routes import BottleRoutes
45
from conans.server.rest.controllers.controller import Controller
5-
from conans.server.rest.controllers.routes import Router
66
from conans.server.service.service import SearchService
77

88

@@ -12,7 +12,7 @@ class SearchController(Controller):
1212
"""
1313
def attach_to(self, app):
1414

15-
r = Router(self.route)
15+
r = BottleRoutes(self.route)
1616

1717
@app.route('%s/search' % r.base_url, method=["GET"])
1818
def search(auth_user):

‎conans/server/rest/controllers/v2/conan_controller.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from conans.errors import NotFoundException
44
from conans.model.ref import ConanFileReference
5+
from conans.server.rest.bottle_routes import BottleRoutes
56
from conans.server.rest.controllers.controller import Controller
6-
from conans.server.rest.controllers.routes import Router
77
from conans.server.rest.controllers.v2 import get_package_ref
88
from conans.server.service.service_v2 import ConanServiceV2
99

@@ -13,11 +13,11 @@ class ConanControllerV2(Controller):
1313
def attach_to(self, app):
1414

1515
conan_service = ConanServiceV2(app.authorizer, app.server_store)
16-
r = Router(self.route)
16+
r = BottleRoutes(self.route)
1717

18-
@app.route(r.package, method=["GET"])
19-
@app.route(r.package_recipe_revision, method=["GET"])
20-
@app.route(r.package_revision, method=["GET"])
18+
@app.route(r.package_files, method=["GET"])
19+
@app.route(r.package_recipe_revision_files, method=["GET"])
20+
@app.route(r.package_revision_files, method=["GET"])
2121
def get_package_file_list(name, version, username, channel, package_id, auth_user,
2222
revision=None, p_revision=None):
2323
package_reference = get_package_ref(name, version, username, channel, package_id,
@@ -46,8 +46,8 @@ def upload_package_file(name, version, username, channel, package_id,
4646
conan_service.upload_package_file(request.body, request.headers, package_reference,
4747
the_path, auth_user)
4848

49-
@app.route(r.recipe, method=["GET"])
50-
@app.route(r.recipe_revision, method=["GET"])
49+
@app.route(r.recipe_files, method=["GET"])
50+
@app.route(r.recipe_revision_files, method=["GET"])
5151
def get_recipe_file_list(name, version, username, channel, auth_user, revision=None):
5252

5353
reference = ConanFileReference(name, version, username, channel, revision)

‎conans/server/rest/controllers/v2/delete_controller.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from conans.model.ref import ConanFileReference
2+
from conans.server.rest.bottle_routes import BottleRoutes
23
from conans.server.rest.controllers.controller import Controller
3-
from conans.server.rest.controllers.routes import Router
44
from conans.server.rest.controllers.v2 import get_package_ref
55
from conans.server.service.service import ConanService
66

@@ -11,7 +11,7 @@ class DeleteControllerV2(Controller):
1111
"""
1212
def attach_to(self, app):
1313

14-
r = Router(self.route)
14+
r = BottleRoutes(self.route)
1515

1616
@app.route(r.recipe, method="DELETE")
1717
@app.route(r.recipe_revision, method="DELETE")

‎conans/server/rest/controllers/v2/search_controller.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from bottle import request
22

33
from conans.model.ref import ConanFileReference
4+
from conans.server.rest.bottle_routes import BottleRoutes
45
from conans.server.rest.controllers.controller import Controller
5-
from conans.server.rest.controllers.routes import Router
66
from conans.server.service.service import SearchService
77

88

@@ -12,7 +12,7 @@ class SearchControllerV2(Controller):
1212
"""
1313
def attach_to(self, app):
1414

15-
r = Router(self.route)
15+
r = BottleRoutes(self.route)
1616

1717
@app.route('%s/search' % r.base_url, method=["GET"])
1818
def search(auth_user):

‎conans/server/server_launcher.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
#!/usr/bin/python
22
import os
3-
from conans.server.service.authorize import BasicAuthorizer, BasicAuthenticator
3+
4+
from conans import SERVER_CAPABILITIES
5+
from conans import __version__ as SERVER_VERSION, REVISIONS
6+
from conans.model.version import Version
7+
from conans.paths import conan_expand_user
8+
from conans.server.conf import MIN_CLIENT_COMPATIBLE_VERSION
49
from conans.server.conf import get_server_store
5-
from conans.server.rest.server import ConanServer
610
from conans.server.crypto.jwt.jwt_credentials_manager import JWTCredentialsManager
711
from conans.server.crypto.jwt.jwt_updown_manager import JWTUpDownAuthManager
8-
from conans.server.conf import MIN_CLIENT_COMPATIBLE_VERSION
9-
from conans.server.plugin_loader import load_authentication_plugin
10-
from conans.model.version import Version
1112
from conans.server.migrate import migrate_and_get_server_config
12-
from conans import __version__ as SERVER_VERSION, REVISIONS
13-
from conans.paths import conan_expand_user
14-
from conans import SERVER_CAPABILITIES
13+
from conans.server.plugin_loader import load_authentication_plugin
14+
from conans.server.rest.server import ConanServer
15+
from conans.server.service.authorize import BasicAuthorizer, BasicAuthenticator
1516

1617

1718
class ServerLauncher(object):

0 commit comments

Comments
 (0)
Please sign in to comment.