Miscellaneous utilities

coaster.utils.misc.base_domain_matches(d1, d2)[source]

Check if two domains have the same base domain, using the Public Suffix List.

>>> base_domain_matches('https://hasjob.co', 'hasjob.co')
True
>>> base_domain_matches('hasgeek.hasjob.co', 'hasjob.co')
True
>>> base_domain_matches('hasgeek.com', 'hasjob.co')
False
>>> base_domain_matches('static.hasgeek.co.in', 'hasgeek.com')
False
>>> base_domain_matches('static.hasgeek.co.in', 'hasgeek.co.in')
True
>>> base_domain_matches('example@example.com', 'example.com')
True
coaster.utils.misc.buid()

Legacy name

coaster.utils.misc.buid2uuid(value)

Legacy name

coaster.utils.misc.domain_namespace_match(domain, namespace)[source]

Checks if namespace is related to the domain because the base domain matches.

>>> domain_namespace_match('hasgeek.com', 'com.hasgeek')
True
>>> domain_namespace_match('funnel.hasgeek.com', 'com.hasgeek.funnel')
True
>>> domain_namespace_match('app.hasgeek.com', 'com.hasgeek.peopleflow')
True
>>> domain_namespace_match('app.hasgeek.in', 'com.hasgeek.peopleflow')
False
>>> domain_namespace_match('peopleflow.local', 'local.peopleflow')
True
coaster.utils.misc.format_currency(value, decimals=2)[source]

Return a number suitably formatted for display as currency, with thousands separated by commas and up to two decimal points.

>>> format_currency(1000)
'1,000'
>>> format_currency(100)
'100'
>>> format_currency(999.95)
'999.95'
>>> format_currency(99.95)
'99.95'
>>> format_currency(100000)
'100,000'
>>> format_currency(1000.00)
'1,000'
>>> format_currency(1000.41)
'1,000.41'
>>> format_currency(23.21, decimals=3)
'23.210'
>>> format_currency(1000, decimals=3)
'1,000'
>>> format_currency(123456789.123456789)
'123,456,789.12'
coaster.utils.misc.get_email_domain(emailaddr)[source]

Return the domain component of an email address. Returns None if the provided string cannot be parsed as an email address.

>>> get_email_domain('test@example.com')
'example.com'
>>> get_email_domain('test+trailing@example.com')
'example.com'
>>> get_email_domain('Example Address <test@example.com>')
'example.com'
>>> get_email_domain('foobar')
>>> get_email_domain('foobar@')
>>> get_email_domain('@foobar')
coaster.utils.misc.getbool(value)[source]

Returns a boolean from any of a range of values. Returns None for unrecognized values. Numbers other than 0 and 1 are considered unrecognized.

>>> getbool(True)
True
>>> getbool(1)
True
>>> getbool('1')
True
>>> getbool('t')
True
>>> getbool(2)
>>> getbool(0)
False
>>> getbool(False)
False
>>> getbool('n')
False
coaster.utils.misc.is_collection(item)[source]

Returns True if the item is a collection class: list, tuple, set, frozenset or any other class that resembles one of these (using abstract base classes).

>>> is_collection(0)
False
>>> is_collection(0.1)
False
>>> is_collection('')
False
>>> is_collection(b'')
False
>>> is_collection({})
False
>>> is_collection({}.keys())
True
>>> is_collection([])
True
>>> is_collection(())
True
>>> is_collection(set())
True
>>> is_collection(frozenset())
True
>>> from coaster.utils import InspectableSet
>>> is_collection(InspectableSet({1, 2}))
True
coaster.utils.misc.make_name(text, delim='-', maxlength=50, checkused=None, counter=2)[source]

Generate an ASCII name slug. If a checkused filter is provided, it will be called with the candidate. If it returns True, make_name will add counter numbers starting from 2 until a suitable candidate is found.

Parameters:
  • delim (string) – Delimiter between words, default ‘-’
  • maxlength (int) – Maximum length of name, default 50
  • checkused – Function to check if a generated name is available for use
  • counter (int) – Starting position for name counter
>>> make_name('This is a title')
'this-is-a-title'
>>> make_name('Invalid URL/slug here')
'invalid-url-slug-here'
>>> make_name('this.that')
'this-that'
>>> make_name('this:that')
'this-that'
>>> make_name("How 'bout this?")
'how-bout-this'
>>> make_name("How’s that?")
'hows-that'
>>> make_name('K & D')
'k-d'
>>> make_name('billion+ pageviews')
'billion-pageviews'
>>> make_name('हिन्दी slug!')
'hindii-slug'
>>> make_name('Talk in español, Kiswahili, 廣州話 and অসমীয়া too.', maxlength=250)
'talk-in-espanol-kiswahili-guang-zhou-hua-and-asmiiyaa-too'
>>> make_name('__name__', delim='_')
'name'
>>> make_name('how_about_this', delim='_')
'how_about_this'
>>> make_name('and-that', delim='_')
'and_that'
>>> make_name('Umlauts in Mötörhead')
'umlauts-in-motorhead'
>>> make_name('Candidate', checkused=lambda c: c in ['candidate'])
'candidate2'
>>> make_name('Candidate', checkused=lambda c: c in ['candidate'], counter=1)
'candidate1'
>>> make_name('Candidate',
...   checkused=lambda c: c in ['candidate', 'candidate1', 'candidate2'], counter=1)
'candidate3'
>>> make_name('Long title, but snipped', maxlength=20)
'long-title-but-snipp'
>>> len(make_name('Long title, but snipped', maxlength=20))
20
>>> make_name('Long candidate', maxlength=10,
...   checkused=lambda c: c in ['long-candi', 'long-cand1'])
'long-cand2'
>>> make_name('Lǝnkǝran')
'lankaran'
>>> make_name('example@example.com')
'example-example-com'
>>> make_name('trailing-delimiter', maxlength=10)
'trailing-d'
>>> make_name('trailing-delimiter', maxlength=9)
'trailing'
>>> make_name('''test this
... newline''')
'test-this-newline'
>>> make_name("testing an emoji😁")
'testing-an-emoji'
>>> make_name('''testing\t\nmore\r\nslashes''')
'testing-more-slashes'
>>> make_name('What if a HTML <tag/>')
'what-if-a-html-tag'
>>> make_name('These are equivalent to \x01 through \x1A')
'these-are-equivalent-to-through'
>>> make_name("feedback;\x00")
'feedback'
coaster.utils.misc.md5sum(data)[source]

Return md5sum of data as a 32-character string.

>>> md5sum('random text')
'd9b9bec3f4cc5482e7c5ef43143e563a'
>>> md5sum('random text')
'd9b9bec3f4cc5482e7c5ef43143e563a'
>>> len(md5sum('random text'))
32
coaster.utils.misc.namespace_from_url(url)[source]

Construct a dotted namespace string from a URL.

coaster.utils.misc.nary_op(f, doc=None)[source]

Decorator to convert a binary operator into a chained n-ary operator.

coaster.utils.misc.newpin(digits=4)[source]

Return a random numeric string with the specified number of digits, default 4.

>>> len(newpin())
4
>>> len(newpin(5))
5
>>> newpin().isdigit()
True
coaster.utils.misc.newsecret()[source]

Make a secret key for non-cryptographic use cases like email account verification. Mashes two UUID4s into a Base58 rendering, between 42 and 44 characters long. The resulting string consists of only ASCII strings and so will typically not be word-wrapped by email clients.

>>> len(newsecret()) in (42, 43, 44)
True
>>> newsecret() == newsecret()
False
coaster.utils.misc.nullint(value)[source]

Return int(value) if bool(value) is not False. Return None otherwise. Useful for coercing optional values to an integer.

>>> nullint('10')
10
>>> nullint('') is None
True
coaster.utils.misc.nullstr(value)[source]

Return unicode(value) if bool(value) is not False. Return None otherwise. Useful for coercing optional values to a string.

>>> nullstr(10) == '10'
True
>>> nullstr('') is None
True
coaster.utils.misc.require_one_of(_return=False, **kwargs)[source]

Validator that raises TypeError unless one and only one parameter is not None. Use this inside functions that take multiple parameters, but allow only one of them to be specified:

def my_func(this=None, that=None, other=None):
    # Require one and only one of `this` or `that`
    require_one_of(this=this, that=that)

    # If we need to know which parameter was passed in:
    param, value = require_one_of(True, this=this, that=that)

    # Carry on with function logic
    pass
Parameters:
  • _return – Return the matching parameter
  • kwargs – Parameters, of which one and only one is mandatory
Returns:

If _return, matching parameter name and value

Return type:

tuple

Raises:

TypeError – If the count of parameters that aren’t None is not 1

coaster.utils.misc.unicode_http_header(value)[source]

Convert an ASCII HTTP header string into a unicode string with the appropriate encoding applied. Expects headers to be RFC 2047 compliant.

>>> unicode_http_header('=?iso-8859-1?q?p=F6stal?=') == 'p\xf6stal'
True
>>> unicode_http_header(b'=?iso-8859-1?q?p=F6stal?=') == 'p\xf6stal'
True
>>> unicode_http_header('p\xf6stal') == 'p\xf6stal'
True
coaster.utils.misc.uuid1mc()[source]

Return a UUID1 with a random multicast MAC id.

>>> isinstance(uuid1mc(), uuid.UUID)
True
coaster.utils.misc.uuid1mc_from_datetime(dt)[source]

Return a UUID1 with a random multicast MAC id and with a timestamp matching the given datetime object or timestamp value.

Warning

This function does not consider the timezone, and is not guaranteed to return a unique UUID. Use under controlled conditions only.

>>> dt = datetime.now()
>>> u1 = uuid1mc()
>>> u2 = uuid1mc_from_datetime(dt)
>>> # Both timestamps should be very close to each other but not an exact match
>>> u1.time > u2.time
True
>>> u1.time - u2.time < 5000
True
>>> d2 = datetime.fromtimestamp((u2.time - 0x01b21dd213814000) * 100 / 1e9)
>>> d2 == dt
True
coaster.utils.misc.uuid2buid(value)

Legacy name

coaster.utils.misc.uuid_b58()[source]

Return a UUID4 encoded in base58 and rendered as a string. Will be 21 or 22 characters long

>>> len(uuid_b58()) in (21, 22)
True
>>> uuid_b58() == uuid_b58()
False
>>> isinstance(uuid_b58(), str)
True
coaster.utils.misc.uuid_b64()[source]

Return a new random id that is exactly 22 characters long, by encoding a UUID4 in URL-safe Base64. See http://en.wikipedia.org/wiki/Base64#Variants_summary_table

>>> len(buid())
22
>>> buid() == buid()
False
>>> isinstance(buid(), str)
True
coaster.utils.misc.uuid_from_base58(value)[source]

Convert a Base58-encoded UUID back into a UUID object

>>> uuid_from_base58('7KAmj837MyuJWUYPwtqAfz')
UUID('33203dd2-f2ef-422f-aeb0-058d6f5f7089')
>>> # The following UUID to Base58 encoding is from NPM uuid-base58, for comparison
>>> uuid_from_base58('TedLUruK7MosG1Z88urTkk')
UUID('d7ce8475-e77c-43b0-9dde-56b428981999')
coaster.utils.misc.uuid_from_base64(value)[source]

Convert a 22-char URL-safe Base64 string (BUID) to a UUID object

>>> uuid_from_base64('MyA90vLvQi-usAWNb19wiQ')
UUID('33203dd2-f2ef-422f-aeb0-058d6f5f7089')
coaster.utils.misc.uuid_to_base58(value)[source]

Render a UUID in Base58 and return as a string

>>> uuid_to_base58(uuid.UUID('33203dd2-f2ef-422f-aeb0-058d6f5f7089'))
'7KAmj837MyuJWUYPwtqAfz'
>>> # The following UUID to Base58 encoding is from NPM uuid-base58, for comparison
>>> uuid_to_base58(uuid.UUID('d7ce8475-e77c-43b0-9dde-56b428981999'))
'TedLUruK7MosG1Z88urTkk'
coaster.utils.misc.uuid_to_base64(value)[source]

Convert a UUID object to a 22-char URL-safe Base64 string (BUID)

>>> uuid_to_base64(uuid.UUID('33203dd2-f2ef-422f-aeb0-058d6f5f7089'))
'MyA90vLvQi-usAWNb19wiQ'
coaster.utils.misc.valid_username(candidate)[source]

Check if a username is valid.

>>> valid_username('example person')
False
>>> valid_username('example_person')
False
>>> valid_username('exampleperson')
True
>>> valid_username('example-person')
True
>>> valid_username('a')
True
>>> (valid_username('a-') or valid_username('ab-') or valid_username('-a') or
...   valid_username('-ab'))
False