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:
Russell Ballestrini 2024-09-25 10:02:57 -04:00
parent 143127cf7a
commit 06e48daa4d
4 changed files with 161 additions and 95 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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/*