New Release. More unit tests and fix ups!
modified: README.rst modified: miniuri/miniuri.py modified: miniuri/tests/test_miniuri.py modified: setup.py
This commit is contained in:
parent
143127cf7a
commit
06e48daa4d
4 changed files with 161 additions and 95 deletions
|
|
@ -3,7 +3,7 @@ miniuri
|
|||
|
||||
miniuri is a universal URI parser class.
|
||||
|
||||
Only 145 lines of Python (400 lines if you count unit tests).
|
||||
Only 150 lines of Python (486 lines if you count unit tests).
|
||||
|
||||
|
||||
Installation
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
class Uri(object):
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class Uri:
|
||||
"""
|
||||
miniuri is a universal URI parser class.
|
||||
The parser grants access to the following attributes:
|
||||
|
||||
foo://username:password@test.com:808/go/to/index.php?pet=cat&name=bam#eye
|
||||
\_/ \_______________/ \______/ \_/ \___/ \_/ \_______________/\_/
|
||||
| | | | | | | |
|
||||
| | | | | | | |
|
||||
| userinfo hostname | | | query fragment
|
||||
| \___________________________|/\________|____|_/
|
||||
| | | | | |
|
||||
|
|
@ -14,136 +17,134 @@ class Uri(object):
|
|||
port filename
|
||||
"""
|
||||
|
||||
def _reset_attrs(self):
|
||||
self.scheme = self.username = self.password = None
|
||||
self.hostname = self.port = self.path = None
|
||||
self.filename = self.query = self.fragment = None
|
||||
def __init__(self, uri=None):
|
||||
self._reset_attributes()
|
||||
if uri:
|
||||
self.uri = uri
|
||||
|
||||
def _reset_attributes(self):
|
||||
self.scheme = None
|
||||
self.username = None
|
||||
self.password = None
|
||||
self.hostname = None
|
||||
self.port = None
|
||||
self._path = None
|
||||
self._filename = None
|
||||
self.query = None
|
||||
self.fragment = None
|
||||
self.extension = None
|
||||
|
||||
def __init__(self, uri=None):
|
||||
self._reset_attrs()
|
||||
if uri:
|
||||
self.uri = uri # invoke uri.setter
|
||||
|
||||
def __str__(self):
|
||||
return self.uri
|
||||
return self.uri or ""
|
||||
|
||||
@property
|
||||
def uri(self):
|
||||
"""build and return uri from attributes"""
|
||||
scheme = self.scheme + "://" if self.scheme else ""
|
||||
authority = self.authority if self.authority else ""
|
||||
return "".join([scheme, authority, self.relative_uri])
|
||||
"""Build and return URI from attributes."""
|
||||
scheme = "{}://".format(self.scheme) if self.scheme else ""
|
||||
authority = self.authority or ""
|
||||
return "{}{}{}".format(scheme, authority, self.relative_uri)
|
||||
|
||||
@uri.setter
|
||||
def uri(self, uri):
|
||||
"""parse and set all uri attributes"""
|
||||
self._reset_attrs()
|
||||
|
||||
if uri == "" or uri is None:
|
||||
return None
|
||||
|
||||
"""Parse and set all URI attributes."""
|
||||
self._reset_attributes()
|
||||
if not uri:
|
||||
return
|
||||
if "://" in uri:
|
||||
self.scheme, uri = uri.split("://")
|
||||
|
||||
self.scheme, uri = uri.split("://", 1)
|
||||
elif uri.startswith("//"):
|
||||
uri = uri.lstrip("//")
|
||||
if "#" in uri:
|
||||
uri, self.fragment = uri.split("#")
|
||||
|
||||
uri, self.fragment = uri.split("#", 1)
|
||||
if "?" in uri:
|
||||
uri, self.query = uri.split("?")
|
||||
|
||||
# invoke authority setter.
|
||||
self.authority = uri.split("/")[0]
|
||||
|
||||
# invoke path setter.
|
||||
offset = 0
|
||||
if self.authority:
|
||||
offset = len(self.authority)
|
||||
self.path = uri[offset:]
|
||||
uri, self.query = uri.split("?", 1)
|
||||
parts = uri.split("/", 1)
|
||||
self.authority = parts[0]
|
||||
self.path = "/" + parts[1] if len(parts) > 1 else None
|
||||
|
||||
@property
|
||||
def authority(self):
|
||||
"""return a authority string from attributes"""
|
||||
"""Return an authority string from attributes."""
|
||||
if self.hostname:
|
||||
a = ""
|
||||
authority = ""
|
||||
if self.username:
|
||||
a += self.username
|
||||
authority += self.username
|
||||
if self.password:
|
||||
a += ":" + self.password
|
||||
a += "@"
|
||||
a += self.hostname
|
||||
authority += ":{}".format(self.password)
|
||||
authority += "@"
|
||||
authority += self.hostname
|
||||
if self.port:
|
||||
a += ":" + self.port
|
||||
return a
|
||||
authority += ":{}".format(self.port)
|
||||
return authority
|
||||
|
||||
@authority.setter
|
||||
def authority(self, a):
|
||||
"""set all the attribute that makeup a authority"""
|
||||
if a:
|
||||
def authority(self, authority):
|
||||
"""Set all the attributes that make up an authority."""
|
||||
if authority:
|
||||
self.username = self.password = self.port = None
|
||||
self.hostname = a
|
||||
self.hostname = authority
|
||||
if "@" in self.hostname:
|
||||
self.userinfo, self.hostname = a.split("@") # userinfo setter
|
||||
self.userinfo, self.hostname = authority.split("@", 1)
|
||||
if ":" in self.hostname:
|
||||
self.hostname, self.port = self.hostname.split(":")
|
||||
self.hostname, self.port = self.hostname.split(":", 1)
|
||||
if not self.hostname:
|
||||
self.hostname = None
|
||||
|
||||
@property
|
||||
def relative_uri(self):
|
||||
"""return everything that isn't part of the authority."""
|
||||
path = query = fragment = ""
|
||||
if self.path:
|
||||
path = self.path
|
||||
if self.query:
|
||||
query = "?" + self.query
|
||||
if self.fragment:
|
||||
fragment = "#" + self.fragment
|
||||
return "".join([path, query, fragment])
|
||||
"""Return everything that isn't part of the authority."""
|
||||
path = self.path or ""
|
||||
query = "?{}".format(self.query) if self.query else ""
|
||||
fragment = "#{}".format(self.fragment) if self.fragment else ""
|
||||
return "{}{}{}".format(path, query, fragment)
|
||||
|
||||
@property
|
||||
def userinfo(self):
|
||||
"""return username:password, username, or None"""
|
||||
"""Return username:password, username, or None."""
|
||||
if self.username:
|
||||
if self.password:
|
||||
return self.username + ":" + self.password
|
||||
return self.username
|
||||
return (
|
||||
"{}:{}".format(self.username, self.password)
|
||||
if self.password
|
||||
else self.username
|
||||
)
|
||||
|
||||
@userinfo.setter
|
||||
def userinfo(self, info):
|
||||
"""set username and password"""
|
||||
self.username, self.password = info, None
|
||||
if ":" in info:
|
||||
self.username, self.password = info.split(":")
|
||||
"""Set username and password."""
|
||||
self.username, self.password = (info.split(":", 1) + [None])[:2]
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""return path"""
|
||||
p = self._path if self._path is not None else ""
|
||||
f = self.filename if self.filename is not None else ""
|
||||
if p or f:
|
||||
return p + f
|
||||
"""Return path."""
|
||||
path = self._path or ""
|
||||
filename = self.filename or ""
|
||||
return "{}{}".format(path, filename) if path or filename else None
|
||||
|
||||
@path.setter
|
||||
def path(self, new_path):
|
||||
if new_path:
|
||||
self.filename = new_path.split("/")[-1]
|
||||
self._path = new_path.rstrip(self.filename)
|
||||
self._path = new_path[: -len(self.filename)] if self.filename else new_path
|
||||
else:
|
||||
self.filename = None
|
||||
self._path = None
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""return filename"""
|
||||
"""Return filename."""
|
||||
if self._filename:
|
||||
if self.extension:
|
||||
return "{}.{}".format(self._filename, self.extension)
|
||||
return self._filename
|
||||
return (
|
||||
"{}.{}".format(self._filename, self.extension)
|
||||
if self.extension
|
||||
else self._filename
|
||||
)
|
||||
|
||||
@filename.setter
|
||||
def filename(self, new_filename):
|
||||
if new_filename:
|
||||
self._filename = new_filename.split(".")[0]
|
||||
if "." in new_filename:
|
||||
self.extension = new_filename.split(".")[-1]
|
||||
parts = new_filename.rsplit(".", 1)
|
||||
self._filename = parts[0]
|
||||
self.extension = parts[1] if len(parts) > 1 else None
|
||||
else:
|
||||
self._filename = None
|
||||
self.extension = None
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from ..miniuri import Uri
|
||||
|
||||
|
||||
class TestMiniUri(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# "http://www.foxhop.net/samsung/HL-T5087SA/red-LED-failure"
|
||||
self.u = Uri("https://fox:pass@www.foxhop.net:81/path/filename.jpg?p=2#5")
|
||||
|
|
@ -50,7 +48,7 @@ class TestMiniUri(TestCase):
|
|||
def test_fragment(self):
|
||||
self.assertEqual(self.u.fragment, "5")
|
||||
|
||||
# test edge case URIs
|
||||
# Test edge case URIs
|
||||
|
||||
def test_uri_no_path(self):
|
||||
self.u = Uri("https://fox:pass@www.foxhop.net:81?p=2#5")
|
||||
|
|
@ -59,9 +57,9 @@ class TestMiniUri(TestCase):
|
|||
self.assertEqual(self.u.password, "pass")
|
||||
self.assertEqual(self.u.hostname, "www.foxhop.net")
|
||||
self.assertEqual(self.u.port, "81")
|
||||
self.assertEqual(self.u.path, None)
|
||||
self.assertEqual(self.u.filename, None)
|
||||
self.assertEqual(self.u.extension, None)
|
||||
self.assertIsNone(self.u.path)
|
||||
self.assertIsNone(self.u.filename)
|
||||
self.assertIsNone(self.u.extension)
|
||||
|
||||
def test_uri_filename_only_path(self):
|
||||
self.u = Uri("https://fox:pass@www.foxhop.net:81/filename.jpg?p=2#5")
|
||||
|
|
@ -81,7 +79,7 @@ class TestMiniUri(TestCase):
|
|||
self.assertEqual(self.u.path, "/")
|
||||
self.assertEqual(self.u.uri, "https://fox:pass@www.foxhop.net:81/?p=2#5")
|
||||
|
||||
# test_change_attributes:
|
||||
# Test change attributes:
|
||||
|
||||
def test_change_uri(self):
|
||||
self.u.uri = "http://zell:sapp@wwws.foxhop.net:82/my/file.png?p=3#6"
|
||||
|
|
@ -190,9 +188,10 @@ class TestMiniUri(TestCase):
|
|||
self.assertEqual(u.relative_uri, "/about.html")
|
||||
self.assertEqual(u.filename, "about.html")
|
||||
self.assertEqual(u.extension, "html")
|
||||
self.assertIsNone(u.fragment, None)
|
||||
self.assertIsNone(u.hostname, None)
|
||||
self.assertIsNone(u.scheme, None)
|
||||
self.assertIsNone(u.fragment)
|
||||
self.assertIsNone(u.hostname)
|
||||
self.assertIsNone(u.scheme)
|
||||
self.assertIsNone(u.authority)
|
||||
|
||||
def test_empty_uri_sets_attrs_to_none(self):
|
||||
self.u.uri = ""
|
||||
|
|
@ -210,7 +209,7 @@ class TestMiniUri(TestCase):
|
|||
self.assertIsNone(self.u.authority)
|
||||
|
||||
def test_empty_uri_sets_attrs_to_none2(self):
|
||||
# same as above but tests the constructor.
|
||||
# Same as above but tests the constructor.
|
||||
self.u = Uri("")
|
||||
self.assertEqual(self.u.uri, "")
|
||||
self.assertIsNone(self.u.scheme)
|
||||
|
|
@ -241,7 +240,7 @@ class TestMiniUri(TestCase):
|
|||
self.assertIsNone(self.u.authority)
|
||||
|
||||
def test_none_uri_sets_attrs_to_none2(self):
|
||||
# same as above but tests the constructor.
|
||||
# Same as above but tests the constructor.
|
||||
self.u = Uri(None)
|
||||
self.assertEqual(self.u.uri, "")
|
||||
self.assertIsNone(self.u.scheme)
|
||||
|
|
@ -271,3 +270,67 @@ class TestMiniUri(TestCase):
|
|||
self.assertIsNone(self.u.extension)
|
||||
self.assertIsNone(self.u.authority)
|
||||
|
||||
# New tests for additional edge cases
|
||||
|
||||
def test_no_scheme(self):
|
||||
u = Uri("//www.example.com/path")
|
||||
self.assertIsNone(u.scheme)
|
||||
self.assertEqual(u.authority, "www.example.com")
|
||||
self.assertEqual(u.path, "/path")
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
def test_no_authority(self):
|
||||
u = Uri("/path/to/resource")
|
||||
self.assertIsNone(u.scheme)
|
||||
self.assertIsNone(u.authority)
|
||||
self.assertEqual(u.path, "/path/to/resource")
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
def test_no_path(self):
|
||||
u = Uri("http://hostname")
|
||||
self.assertEqual(u.scheme, "http")
|
||||
self.assertEqual(u.authority, "hostname")
|
||||
self.assertIsNone(u.path)
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
def test_no_query_or_fragment(self):
|
||||
u = Uri("http://hostname/path")
|
||||
self.assertEqual(u.scheme, "http")
|
||||
self.assertEqual(u.authority, "hostname")
|
||||
self.assertEqual(u.path, "/path")
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
def test_unicode_handling(self):
|
||||
u = Uri("http://hostname/路径")
|
||||
self.assertEqual(u.scheme, "http")
|
||||
self.assertEqual(u.authority, "hostname")
|
||||
self.assertEqual(u.path, "/路径")
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
# Test for invalid inputs
|
||||
def test_invalid_uri(self):
|
||||
u = Uri("http://:80")
|
||||
self.assertEqual(u.scheme, "http")
|
||||
self.assertIsNone(u.username)
|
||||
self.assertIsNone(u.password)
|
||||
self.assertIsNone(u.hostname)
|
||||
self.assertEqual(u.port, "80")
|
||||
self.assertIsNone(u.path)
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
||||
def test_malformed_uri(self):
|
||||
u = Uri("http://user@:80")
|
||||
self.assertEqual(u.scheme, "http")
|
||||
self.assertEqual(u.username, "user")
|
||||
self.assertIsNone(u.password)
|
||||
self.assertIsNone(u.hostname)
|
||||
self.assertEqual(u.port, "80")
|
||||
self.assertIsNone(u.path)
|
||||
self.assertIsNone(u.query)
|
||||
self.assertIsNone(u.fragment)
|
||||
|
|
|
|||
6
setup.py
6
setup.py
|
|
@ -4,7 +4,7 @@ from setuptools import setup, find_packages
|
|||
|
||||
setup(
|
||||
name="miniuri",
|
||||
version="1.0.9",
|
||||
version="1.1.0",
|
||||
long_description=open("README.rst").read(),
|
||||
description="miniuri: The Universal URI Parser",
|
||||
keywords="miniuri uri url parser",
|
||||
|
|
@ -31,6 +31,9 @@ setup(
|
|||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -39,4 +42,3 @@ setup(
|
|||
# built and uploaded to pypi with this:
|
||||
#python setup.py sdist bdist_egg
|
||||
#twine upload dist/*
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue