Skip to content

Commit d8bcc86

Browse files
authored
Merge pull request #257 from seequent/feat/web_extras
New extras file for Web properties
2 parents 8459438 + 061068c commit d8bcc86

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed

docs/content/extras.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ importing :code:`properties.extras`.
1010
unique IDs and :code:`Pointer` property to refer to instances by
1111
unique ID.
1212

13+
* :ref:`web` - Web-related Property classes
14+
1315
* :ref:`singleton` - HasProperties class that creates only one instance
1416
for a given identifying name. Any instances with that name will
1517
be the same instance.
@@ -22,5 +24,6 @@ importing :code:`properties.extras`.
2224
:hidden:
2325

2426
./uid
27+
./web
2528
./singleton
2629
./task

docs/content/web.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. _web:
2+
3+
Web-Related Extras
4+
==================
5+
6+
.. autoclass:: properties.extras.URL

properties/extras/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
TaskStatus,
2020
)
2121
from .uid import HasUID, Pointer
22+
from .web import URL

properties/extras/web.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Property classes for web-related concepts"""
2+
from six.moves.urllib.parse import ParseResult, urlparse #pylint: disable=import-error
3+
4+
from .. import basic
5+
6+
class URL(basic.String):
7+
"""String property that only accepts valid URLs
8+
9+
This property type uses :code:`urllib.parse` to validate
10+
input URLs and possibly remove fragments and query params.
11+
12+
**Available keywords** (in addition to those inherited from
13+
:class:`String <properties.String>`):
14+
15+
* **remove_parameters** - Query params are stripped from input URL (default
16+
is False).
17+
* **remove_fragment** - Fragment is stripped from input URL (default
18+
is False).
19+
"""
20+
21+
class_info = 'a URL'
22+
23+
@property
24+
def remove_parameters(self):
25+
"""Should path and query parameters be stripped"""
26+
return getattr(self, '_remove_parameters', False)
27+
28+
@remove_parameters.setter
29+
def remove_parameters(self, value):
30+
self._remove_parameters = bool(value)
31+
32+
@property
33+
def remove_fragment(self):
34+
"""Should fragment be stripped"""
35+
return getattr(self, '_remove_fragment', False)
36+
37+
@remove_fragment.setter
38+
def remove_fragment(self, value):
39+
self._remove_fragment = bool(value)
40+
41+
def validate(self, instance, value):
42+
"""Check if input is valid URL"""
43+
value = super(URL, self).validate(instance, value)
44+
parsed_url = urlparse(value)
45+
if not parsed_url.scheme or not parsed_url.netloc:
46+
self.error(instance, value)
47+
parse_result = ParseResult(
48+
scheme=parsed_url.scheme,
49+
netloc=parsed_url.netloc,
50+
path=parsed_url.path,
51+
params='' if self.remove_parameters else parsed_url.params,
52+
query='' if self.remove_parameters else parsed_url.query,
53+
fragment='' if self.remove_fragment else parsed_url.fragment,
54+
)
55+
parse_result = parse_result.geturl()
56+
return parse_result
57+
58+
@property
59+
def info(self):
60+
info = 'a URL string'
61+
if self.remove_parameters:
62+
info += ', path or query params removed'
63+
if self.remove_fragment:
64+
info += ', fragment removed'

tests/test_web.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import absolute_import
2+
from __future__ import division
3+
from __future__ import print_function
4+
from __future__ import unicode_literals
5+
6+
import unittest
7+
8+
import properties
9+
from properties.extras import URL
10+
11+
BASE = 'https://someurl.com'
12+
PATH = '/my/path'
13+
QUERY = '?one=1&two=2'
14+
PARAM = ';/three;four=4'
15+
FRAGMENT = '#fragment'
16+
17+
class TestURL(unittest.TestCase):
18+
19+
def test_anyurl(self):
20+
21+
class HasURL(properties.HasProperties):
22+
url = URL('any url')
23+
24+
hurl = HasURL()
25+
hurl.url = BASE
26+
assert hurl.url == BASE
27+
28+
full_url = BASE + PATH + QUERY + PARAM + FRAGMENT
29+
hurl = HasURL()
30+
hurl.url = full_url
31+
assert hurl.url == full_url
32+
33+
def test_no_params(self):
34+
35+
class HasURL(properties.HasProperties):
36+
url = URL('any url', remove_parameters=True)
37+
38+
full_url = BASE + PATH + QUERY + PARAM + FRAGMENT
39+
expected_url = BASE + PATH + FRAGMENT
40+
hurl = HasURL()
41+
hurl.url = full_url
42+
assert hurl.url == expected_url
43+
44+
def test_no_fragment(self):
45+
46+
class HasURL(properties.HasProperties):
47+
url = URL('any url', remove_fragment=True)
48+
49+
full_url = BASE + PATH + QUERY + PARAM + FRAGMENT
50+
expected_url = BASE + PATH + QUERY + PARAM
51+
hurl = HasURL()
52+
hurl.url = full_url
53+
assert hurl.url == expected_url
54+
55+
def test_bad_urls(self):
56+
57+
class HasURL(properties.HasProperties):
58+
url = URL('any url')
59+
60+
hurl = HasURL()
61+
for item in (PATH, QUERY, PARAM, FRAGMENT, 'someurl.com', 'https://'):
62+
with self.assertRaises(properties.ValidationError):
63+
hurl.url = item
64+
65+
66+
if __name__ == '__main__':
67+
unittest.main()

0 commit comments

Comments
 (0)