Skip to content

Commit 40a72be

Browse files
committed
Factor out guts of '{topic,subscription}_name_from_path'.
Use a passed-in regex, in spite of Zawinski's law. Put the factored-out code in 'gcloud._helpers', because I already know I need it in logging. Addresses: #1580 (comment) #1580 (comment) #1580 (comment).
1 parent f9e9103 commit 40a72be

File tree

4 files changed

+89
-63
lines changed

4 files changed

+89
-63
lines changed

gcloud/_helpers.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
import calendar
2020
import datetime
2121
import os
22-
from threading import local as Local
22+
import re
2323
import socket
2424
import sys
25+
from threading import local as Local
2526

2627
from google.protobuf import timestamp_pb2
2728
import six
@@ -388,6 +389,43 @@ def _datetime_to_pb_timestamp(when):
388389
return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)
389390

390391

392+
def _name_from_project_path(path, project, template):
393+
"""Validate a URI path and get the leaf object's name.
394+
395+
:type path: string
396+
:param path: URI path containing the name.
397+
398+
:type project: string
399+
:param project: The project associated with the request. It is
400+
included for validation purposes.
401+
402+
:type template: string
403+
:param template: Template regex describing the expected form of the path.
404+
The regex must have two named groups, 'project' and
405+
'name'.
406+
407+
:rtype: string
408+
:returns: Name parsed from ``path``.
409+
:raises: :class:`ValueError` if the ``path`` is ill-formed or if
410+
the project from the ``path`` does not agree with the
411+
``project`` passed in.
412+
"""
413+
if isinstance(template, str):
414+
template = re.compile(template)
415+
416+
match = template.match(path)
417+
418+
if not match:
419+
raise ValueError('path did not match: %s' % (template.pattern))
420+
421+
found_project = match.group('project')
422+
if found_project != project:
423+
raise ValueError('Project from client should agree with '
424+
'project from resource.')
425+
426+
return match.group('name')
427+
428+
391429
try:
392430
from pytz import UTC # pylint: disable=unused-import,wrong-import-order
393431
except ImportError:

gcloud/pubsub/_helpers.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414

1515
"""Helper functions for shared behavior."""
1616

17+
import re
18+
19+
from gcloud._helpers import _name_from_project_path
20+
21+
22+
_TOPIC_PATH_TEMPLATE = re.compile(
23+
r'projects/(?P<project>\w+)/topics/(?P<name>\w+)')
24+
1725

1826
def topic_name_from_path(path, project):
1927
"""Validate a topic URI path and get the topic name.
@@ -31,18 +39,11 @@ def topic_name_from_path(path, project):
3139
the project from the ``path`` does not agree with the
3240
``project`` passed in.
3341
"""
34-
# PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
35-
path_parts = path.split('/')
36-
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
37-
path_parts[2] != 'topics'):
38-
raise ValueError('Expected path to be of the form '
39-
'projects/{project}/topics/{topic_name}')
40-
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
41-
path_parts[2] != 'topics' or path_parts[1] != project):
42-
raise ValueError('Project from client should agree with '
43-
'project from resource.')
42+
return _name_from_project_path(path, project, _TOPIC_PATH_TEMPLATE)
43+
4444

45-
return path_parts[3]
45+
_SUBSCRIPTION_PATH_TEMPLATE = re.compile(
46+
r'projects/(?P<project>\w+)/subscriptions/(?P<name>\w+)')
4647

4748

4849
def subscription_name_from_path(path, project):
@@ -61,16 +62,4 @@ def subscription_name_from_path(path, project):
6162
the project from the ``path`` does not agree with the
6263
``project`` passed in.
6364
"""
64-
# PATH = 'projects/%s/subscriptions/%s' % (PROJECT, TOPIC_NAME)
65-
path_parts = path.split('/')
66-
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
67-
path_parts[2] != 'subscriptions'):
68-
raise ValueError(
69-
'Expected path to be of the form '
70-
'projects/{project}/subscriptions/{subscription_name}')
71-
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
72-
path_parts[2] != 'subscriptions' or path_parts[1] != project):
73-
raise ValueError('Project from client should agree with '
74-
'project from resource.')
75-
76-
return path_parts[3]
65+
return _name_from_project_path(path, project, _SUBSCRIPTION_PATH_TEMPLATE)

gcloud/pubsub/test__helpers.py

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,7 @@ def _callFUT(self, path, project):
2121
from gcloud.pubsub._helpers import topic_name_from_path
2222
return topic_name_from_path(path, project)
2323

24-
def test_invalid_path_length(self):
25-
PATH = 'projects/foo'
26-
PROJECT = None
27-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)
28-
29-
def test_invalid_path_format(self):
30-
TOPIC_NAME = 'TOPIC_NAME'
31-
PROJECT = 'PROJECT'
32-
PATH = 'foo/%s/bar/%s' % (PROJECT, TOPIC_NAME)
33-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)
34-
35-
def test_invalid_project(self):
36-
TOPIC_NAME = 'TOPIC_NAME'
37-
PROJECT1 = 'PROJECT1'
38-
PROJECT2 = 'PROJECT2'
39-
PATH = 'projects/%s/topics/%s' % (PROJECT1, TOPIC_NAME)
40-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT2)
41-
42-
def test_valid_data(self):
24+
def test_it(self):
4325
TOPIC_NAME = 'TOPIC_NAME'
4426
PROJECT = 'PROJECT'
4527
PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
@@ -53,25 +35,7 @@ def _callFUT(self, path, project):
5335
from gcloud.pubsub._helpers import subscription_name_from_path
5436
return subscription_name_from_path(path, project)
5537

56-
def test_invalid_path_length(self):
57-
PATH = 'projects/foo'
58-
PROJECT = None
59-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)
60-
61-
def test_invalid_path_format(self):
62-
TOPIC_NAME = 'TOPIC_NAME'
63-
PROJECT = 'PROJECT'
64-
PATH = 'foo/%s/bar/%s' % (PROJECT, TOPIC_NAME)
65-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)
66-
67-
def test_invalid_project(self):
68-
TOPIC_NAME = 'TOPIC_NAME'
69-
PROJECT1 = 'PROJECT1'
70-
PROJECT2 = 'PROJECT2'
71-
PATH = 'projects/%s/subscriptions/%s' % (PROJECT1, TOPIC_NAME)
72-
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT2)
73-
74-
def test_valid_data(self):
38+
def test_it(self):
7539
TOPIC_NAME = 'TOPIC_NAME'
7640
PROJECT = 'PROJECT'
7741
PATH = 'projects/%s/subscriptions/%s' % (PROJECT, TOPIC_NAME)

gcloud/test__helpers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,41 @@ def test_it(self):
526526
self.assertEqual(self._callFUT(dt_stamp), timestamp)
527527

528528

529+
class Test__name_from_project_path(unittest2.TestCase):
530+
531+
PROJECT = 'PROJECT'
532+
THING_NAME = 'THING_NAME'
533+
TEMPLATE = r'projects/(?P<project>\w+)/things/(?P<name>\w+)'
534+
535+
def _callFUT(self, path, project, template):
536+
from gcloud._helpers import _name_from_project_path
537+
return _name_from_project_path(path, project, template)
538+
539+
def test_w_invalid_path_length(self):
540+
PATH = 'projects/foo'
541+
with self.assertRaises(ValueError):
542+
self._callFUT(PATH, None, self.TEMPLATE)
543+
544+
def test_w_invalid_path_segments(self):
545+
PATH = 'foo/%s/bar/%s' % (self.PROJECT, self.THING_NAME)
546+
with self.assertRaises(ValueError):
547+
self._callFUT(PATH, self.PROJECT, self.TEMPLATE)
548+
549+
def test_w_mismatched_project(self):
550+
PROJECT1 = 'PROJECT1'
551+
PROJECT2 = 'PROJECT2'
552+
PATH = 'projects/%s/things/%s' % (PROJECT1, self.THING_NAME)
553+
with self.assertRaises(ValueError):
554+
self._callFUT(PATH, PROJECT2, self.TEMPLATE)
555+
556+
def test_w_valid_data_w_compiled_regex(self):
557+
import re
558+
template = re.compile(self.TEMPLATE)
559+
PATH = 'projects/%s/things/%s' % (self.PROJECT, self.THING_NAME)
560+
name = self._callFUT(PATH, self.PROJECT, template)
561+
self.assertEqual(name, self.THING_NAME)
562+
563+
529564
class _AppIdentity(object):
530565

531566
def __init__(self, app_id):

0 commit comments

Comments
 (0)