3
\$\begingroup\$

I have to construct a URL string from smaller substrings. I'll usually have a resource url, an endpoint, a query, but you can imagine even more parts in creating a full URL.

If, for example, I have this resource URL: http://foo.com, this endpoint: bar/v1, and this query: q?var1=33,var2='abc', I expect the final URL to be:

http://foo.com/bar/v1/q?var1=33,var2='abc'

If the strings are formatted following a simple convention (for example, only have leading slashes on a substring) I could simply concatenate them together. The problem however is that these strings will be given as arguments to some library function calls, and hence it is very probable that some library users will miss the convention. It's so easy to add a trailing '/' or omit a leading '/'. So I do not want to impose a convention on this. Instead, I prefer to check and sanitise the arguments. I thought that urllib.parse.urljoin() would serve my purpose, but it does not. So I wrote a simple little method:

def slash_join(*args):
    '''
    Joins a set of strings with a slash (/) between them. Useful for creating URLs.
    If the strings already have a trailing or leading slash, it is ignored.
    Note that the python's urllib.parse.urljoin() does not offer this functionality. 
    '''
    stripped_strings = []
    # strip any leading or trailing slashes
    for a in args:
        if a[0] == '/': start = 1
        else:           start = 0
        if a[-1] =='/':
            stripped_strings.append(a[start:-1])
        else:
            stripped_strings.append(a[start:])

    return '/'.join(stripped_strings)

Usage

>>> slash_join('http://foo.bar/', '/path/', '/query')
>>> 'http://foo.bar/path/query'

>>> slash_join('http://foo.bar', 'path', 'query')
>>> 'http://foo.bar/path/query'

>>> slash_join('http://foo.bar', 'path/', 'query/')
>>> 'http://foo.bar/path/query'

It works fine, but I was wondering if there is a more pythonic way of expressing this, or if I indeed missed a standard library method call that could have helped me.

\$\endgroup\$
5
\$\begingroup\$

You can simplify the solution if, instead of checking for a presence of the slash, you would str.strip() the slashes from both sides of each argument and then str.join() the arguments:

def slash_join(*args):
    return "/".join(arg.strip("/") for arg in args)

urljoin() unfortunately does not allow to join more than 2 url parts - it can only do a base and one more part. There is though this clever functools.reduce() usage that may help to adapt it here:

from urllib.parse import urljoin
from functools import reduce

def slash_join(*args):
    return reduce(urljoin, args).rstrip("/")

Note that I'm explicitly stripping a possible right slash but not sure if it is actually needed since the url with a trailing slash would still be valid - e.g. http://foo.com/test/.

\$\endgroup\$
  • \$\begingroup\$ Thank you, the strip function makes my job much easier and cleaner. \$\endgroup\$ – Thanassis Sep 12 '17 at 2:34
  • 2
    \$\begingroup\$ The main problem with urljoin is not that it takes two arguments (this could be fixed by using reduce as you write). The main problem is that urljoin('http://foo.bar/resource', '/path1') returns 'http://foo.bar/path1. If we stripped leading '/' it could have worked, but I am not sure there is an advantage over simple string joining. \$\endgroup\$ – Thanassis Sep 12 '17 at 2:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.