Utilities

These functions are not dependent on Flask. They implement common patterns in Flask-based applications.

class coaster.utils.LabeledEnum

Labeled enumerations. Declarate an enumeration with values and labels (for use in UI):

>>> class MY_ENUM(LabeledEnum):
...     FIRST = (1, "First")
...     THIRD = (3, "Third")
...     SECOND = (2, "Second")

LabeledEnum will convert any attribute that is a 2-tuple into a value and label pair. Access values as direct attributes of the enumeration:

>>> MY_ENUM.FIRST
1
>>> MY_ENUM.SECOND
2
>>> MY_ENUM.THIRD
3

Access labels via dictionary lookup on the enumeration:

>>> MY_ENUM[MY_ENUM.FIRST]
'First'
>>> MY_ENUM[2]
'Second'
>>> MY_ENUM.get(3)
'Third'
>>> MY_ENUM.get(4) is None
True

Retrieve a full list of values and labels with .items(). Items are sorted by value regardless of the original definition order since Python doesn’t provide a way to preserve that order:

>>> MY_ENUM.items()
[(1, 'First'), (2, 'Second'), (3, 'Third')]
>>> MY_ENUM.keys()
[1, 2, 3]
>>> MY_ENUM.values()
['First', 'Second', 'Third']

However, if you really want manual sorting, add an __order__ list. Anything not in it will be sorted by value as usual:

>>> class RSVP(LabeledEnum):
...     RSVP_Y = ('Y', "Yes")
...     RSVP_N = ('N', "No")
...     RSVP_M = ('M', "Maybe")
...     RSVP_B = ('U', "Unknown")
...     RSVP_A = ('A', "Awaiting")
...     __order__ = (RSVP_Y, RSVP_N, RSVP_M)

>>> RSVP.items()
[('Y', 'Yes'), ('N', 'No'), ('M', 'Maybe'), ('A', 'Awaiting'), ('U', 'Unknown')]

Three value tuples are assumed to be (value, name, title) and the name and title are converted into NameTitle(name, title):

>>> class NAME_ENUM(LabeledEnum):
...     FIRST = (1, 'first', "First")
...     THIRD = (3, 'third', "Third")
...     SECOND = (2, 'second', "Second")

>>> NAME_ENUM.FIRST
1
>>> NAME_ENUM[NAME_ENUM.FIRST]
NameTitle(name='first', title='First')
>>> NAME_ENUM[NAME_ENUM.SECOND].name
'second'
>>> NAME_ENUM[NAME_ENUM.THIRD].title
'Third'

Given a name, the value can be looked up:

>>> NAME_ENUM.value_for('first')
1
>>> NAME_ENUM.value_for('second')
2
class coaster.utils.NameTitle(name, title)
name

Alias for field number 0

title

Alias for field number 1

coaster.utils.base_domain_matches(d1, d2)

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.buid()

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(newid())
22
>>> newid() == newid()
False
>>> isinstance(newid(), unicode)
True
coaster.utils.check_password(reference, attempt)

Compare a reference password with the user attempt.

>>> check_password('{PLAIN}foo', 'foo')
True
>>> check_password(u'{PLAIN}bar', 'bar')
True
>>> check_password(u'{UNKNOWN}baz', 'baz')
False
>>> check_password(u'no-encoding', u'no-encoding')
False
>>> check_password(u'{SSHA}q/uVU8r15k/9QhRi92CWUwMJu2DM6TUSpp25', u're-foo')
True
>>> check_password('{SSHA}q/uVU8r15k/9QhRi92CWUwMJu2DM6TUSpp25', 're-foo')
True
coaster.utils.deobfuscate_email(text)

Deobfuscate email addresses in provided text

coaster.utils.domain_namespace_match(domain, namespace)

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.for_tsquery(text)

Tokenize text into a valid PostgreSQL to_tsquery query.

>>> for_tsquery(" ")
''
>>> for_tsquery("This is a test")
"'This is a test'"
>>> for_tsquery('Match "this AND phrase"')
"'Match this'&'phrase'"
>>> for_tsquery('Match "this & phrase"')
"'Match this'&'phrase'"
>>> for_tsquery("This NOT that")
"'This'&!'that'"
>>> for_tsquery("This & NOT that")
"'This'&!'that'"
>>> for_tsquery("This > that")
"'This > that'"
>>> for_tsquery("Ruby AND (Python OR JavaScript)")
"'Ruby'&('Python'|'JavaScript')"
>>> for_tsquery("Ruby AND NOT (Python OR JavaScript)")
"'Ruby'&!('Python'|'JavaScript')"
>>> for_tsquery("Ruby NOT (Python OR JavaScript)")
"'Ruby'&!('Python'|'JavaScript')"
>>> for_tsquery("Ruby (Python OR JavaScript) Golang")
"'Ruby'&('Python'|'JavaScript')&'Golang'"
>>> for_tsquery("Ruby (Python OR JavaScript) NOT Golang")
"'Ruby'&('Python'|'JavaScript')&!'Golang'"
>>> for_tsquery("Java*")
"'Java':*"
>>> for_tsquery("Java**")
"'Java':*"
>>> for_tsquery("Android || Python")
"'Android'|'Python'"
>>> for_tsquery("Missing (bracket")
"'Missing'&('bracket')"
>>> for_tsquery("Extra bracket)")
"('Extra bracket')"
>>> for_tsquery("Android (Python ())")
"'Android'&('Python')"
>>> for_tsquery("Android (Python !())")
"'Android'&('Python')"
>>> for_tsquery("()")
''
>>> for_tsquery("() Python")
"'Python'"
>>> for_tsquery("!() Python")
"'Python'"
>>> for_tsquery("*")
''
coaster.utils.format_currency(value, decimals=2)

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

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

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('foobar')
>>> get_email_domain('foo@bar@baz')
>>> get_email_domain('foobar@')
>>> get_email_domain('@foobar')
coaster.utils.getbool(value)

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.make_name(text, delim=u'-', maxlength=50, checkused=None, counter=2)

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(u"How’s that?")
u'hows-that'
>>> make_name(u'K & D')
u'k-d'
>>> make_name('billion+ pageviews')
'billion-pageviews'
>>> make_name(u'हिन्दी slug!')
u'hindii-slug'
>>> make_name(u'__name__', delim=u'_')
u'name'
>>> make_name(u'how_about_this', delim=u'_')
u'how_about_this'
>>> make_name(u'and-that', delim=u'_')
u'and_that'
>>> make_name(u'Umlauts in Mötörhead')
u'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(u'Lǝnkǝran')
u'lankaran'
>>> make_name(u'example@example.com')
u'example-example-com'
coaster.utils.make_password(password, encoding=u'BCRYPT')

Make a password with PLAIN, SSHA or BCRYPT (default) encoding.

>>> make_password('foo', encoding='PLAIN')
u'{PLAIN}foo'
>>> make_password(u'bar', encoding='PLAIN')
u'{PLAIN}bar'
>>> make_password(u're-foo', encoding='SSHA')[:6]
u'{SSHA}'
>>> make_password('bar-foo', encoding='SSHA')[:6]
u'{SSHA}'
>>> make_password(u're-foo')[:8]
u'{BCRYPT}'
>>> make_password('bar-foo')[:8]
u'{BCRYPT}'
>>> make_password('foo') == make_password('foo')
False
>>> check_password(make_password('ascii'), 'ascii')
True
>>> check_password(make_password('mixed'), u'mixed')
True
>>> check_password(make_password(u'unicode'), u'unicode')
True
coaster.utils.md5sum(data)

Return md5sum of data as a 32-character string.

>>> md5sum('random text')
'd9b9bec3f4cc5482e7c5ef43143e563a'
>>> md5sum(u'random text')
'd9b9bec3f4cc5482e7c5ef43143e563a'
>>> len(md5sum('random text'))
32
coaster.utils.namespace_from_url(url)

Construct a dotted namespace string from a URL.

coaster.utils.newid()

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(newid())
22
>>> newid() == newid()
False
>>> isinstance(newid(), unicode)
True
coaster.utils.newpin(digits=4)

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.newsecret()

Make a secret key for email confirmation and all that stuff. 44 characters long.

>>> len(newsecret())
44
>>> newsecret() == newsecret()
False
coaster.utils.nullint(value)

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.nullstr(value)

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

>>> nullstr(10)
'10'
>>> nullstr('') is None
True
coaster.utils.nullunicode(value)

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

>>> nullunicode(10)
u'10'
>>> nullunicode('') is None
True
coaster.utils.sanitize_html(value, valid_tags={'em': [], 'pre': [], 'code': [], 'h3': [], 'h6': [], 'h4': [], 'h5': [], 'mark': [], 'strong': [], 'sub': [], 'img': ['src', 'width', 'height', 'align', 'alt'], 'ul': [], 'li': ['start'], 'sup': [], 'cite': [], 'dl': [], 'blockquote': [], 'hr': [], 'dd': [], 'ol': [], 'abbr': ['title'], 'br': [], 'dt': [], 'ins': [], 'a': ['href', 'title', 'target', 'rel'], 'b': [], 'i': [], 'p': [], 'del': []}, strip=True)

Strips unwanted markup out of HTML.

coaster.utils.simplify_text(text)

Simplify text to allow comparison.

>>> simplify_text("Awesome Coder wanted at Awesome Company")
'awesome coder wanted at awesome company'
>>> simplify_text("Awesome Coder, wanted  at Awesome Company! ")
'awesome coder wanted at awesome company'
>>> simplify_text(u"Awesome Coder, wanted  at Awesome Company! ")
u'awesome coder wanted at awesome company'
coaster.utils.sorted_timezones()

Return a list of timezones sorted by offset from UTC.

coaster.utils.valid_username(candidate)

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
coaster.utils.word_count(text, html=True)

Return the count of words in the given text. If the text is HTML (default True), tags are stripped before counting. Handles punctuation and bad formatting like.this when counting words, but assumes conventions for Latin script languages. May not be reliable for other languages.