Start adding a URIBuilder object

This commit is contained in:
Ian Cordasco 2017-03-11 20:00:16 -06:00
parent 0035c8e53c
commit 5aab5ae8b4
No known key found for this signature in database
GPG Key ID: 656D3395E4A9791A
4 changed files with 208 additions and 1 deletions

116
src/rfc3986/builder.py Normal file
View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Ian Cordasco
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module containing the logic for the URIBuilder object."""
from . import normalizers
class URIBuilder(object):
"""Object to aid in building up a URI Reference from parts.
.. note::
This object should be instantiated by the user, but it's recommended
that it is not provided with arguments. Instead, use the available
method to populate the fields.
"""
def __init__(self, scheme=None, userinfo=None, host=None, port=None,
path=None, query=None, fragment=None):
"""Initialize our URI builder.
:param str scheme:
(optional)
:param str userinfo:
(optional)
:param str host:
(optional)
:param int port:
(optional)
:param str path:
(optional)
:param str query:
(optional)
:param str fragment:
(optional)
"""
self.scheme = scheme
self.userinfo = userinfo
self.host = host
self.port = port
self.path = path
self.query = query
self.fragment = fragment
def __repr__(self):
"""Provide a convenient view of our builder object."""
formatstr = ('URIBuilder(scheme={b.scheme}, userinfo={b.userinfo}, '
'host={b.host}, port={b.port}, path={b.path}, '
'query={b.query}, fragment={b.fragment})')
return formatstr.format(b=self)
def add_scheme(self, scheme):
"""Add a scheme to our builder object.
After normalizing, this will generate a new URIBuilder instance with
the specified scheme and all other attributes the same.
.. code-block:: python
>>> URIBuilder().add_scheme('HTTPS')
URIBuilder(scheme='https', userinfo=None, host=None, port=None,
path=None, query=None, fragment=None)
"""
scheme = normalizers.normalize_scheme(scheme)
return URIBuilder(
scheme=scheme,
userinfo=self.userinfo,
host=self.host,
port=self.port,
path=self.path,
query=self.query,
fragment=self.fragment,
)
def add_credentials(self, username, password):
"""Add credentials as the userinfo portion of the URI.
.. code-block:: python
>>> URIBuilder().add_credentials('root', 's3crete')
URIBuilder(scheme=None, userinfo='root:s3crete', host=None,
port=None, path=None, query=None, fragment=None)
>>> URIBuilder().add_credentials('root', None)
URIBuilder(scheme=None, userinfo='root', host=None,
port=None, path=None, query=None, fragment=None)
"""
if username is None:
raise ValueError('Username cannot be None')
userinfo = normalizers.normalize_username(username)
if password is not None:
userinfo += ':{}'.format(normalizers.normalize_password(password))
return URIBuilder(
scheme=self.scheme,
userinfo=userinfo,
host=self.host,
port=self.port,
path=self.path,
query=self.query,
fragment=self.fragment,
)

View File

@ -15,8 +15,22 @@
"""Compatibility module for Python 2 and 3 support."""
import sys
try:
from urllib.parse import quote as urlquote
except ImportError: # Python 2.x
from urllib import quote as urlquote
if sys.version_info >= (3, 0):
__all__ = (
'to_bytes',
'to_str',
'urlquote',
)
PY3 = (3, 0) <= sys.version_info < (4, 0)
PY2 = (2, 6) <= sys.version_info < (2, 8)
if PY3:
unicode = str # Python 3.x

View File

@ -37,6 +37,16 @@ def normalize_authority(authority):
return result
def normalize_username(username):
"""Normalize a username to make it safe to include in userinfo."""
return compat.urlquote(username)
def normalize_password(password):
"""Normalize a password to make safe for userinfo."""
return compat.urlquote(password)
def normalize_host(host):
"""Normalize a host string."""
return host.lower()

67
tests/test_builder.py Normal file
View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Ian Cordasco
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module containing the tests for the URIBuilder object."""
import pytest
from rfc3986 import builder
def test_builder_default():
"""Verify the default values."""
uribuilder = builder.URIBuilder()
assert uribuilder.scheme is None
assert uribuilder.userinfo is None
assert uribuilder.host is None
assert uribuilder.port is None
assert uribuilder.path is None
assert uribuilder.query is None
assert uribuilder.fragment is None
def test_repr():
"""Verify our repr looks like our class."""
uribuilder = builder.URIBuilder()
assert repr(uribuilder).startswith('URIBuilder(scheme=None')
@pytest.mark.parametrize('scheme', [
'https',
'hTTps',
'Https',
'HtTpS',
'HTTPS',
])
def test_add_scheme(scheme):
"""Verify schemes are normalized when added."""
uribuilder = builder.URIBuilder().add_scheme(scheme)
assert uribuilder.scheme == 'https'
@pytest.mark.parametrize('username, password, userinfo', [
('user', 'pass', 'user:pass'),
('user', None, 'user'),
('user@domain.com', 'password', 'user%40domain.com:password'),
('user', 'pass:word', 'user:pass%3Aword'),
])
def test_add_credentials(username, password, userinfo):
"""Verify we normalize usernames and passwords."""
uribuilder = builder.URIBuilder().add_credentials(username, password)
assert uribuilder.userinfo == userinfo
def test_add_credentials_requires_username():
"""Verify one needs a username to add credentials."""
with pytest.raises(ValueError):
builder.URIBuilder().add_credentials(None, None)