Skip to main content

The library provides a lightweight API using which you can in a minute add a pattern processing function "stringify(pattern: str) -> str" to any class.

Project description

Overview

The library allows you to get a string representation of a class at RUNTIME, without overloading __repr__, __str__ or defining your own specific get_repr() -> str (or even several for different cases).
The library provides a lightweight API using which you can in a minute add a pattern processing function stringify(pattern: str) -> str to any class.

This tool is more intended to help developers that want to allow users specify their own data representation patterns.

Motivation

You're a developer. Let's assume that song_downloader is a library you wrote using strify. song_downloader.search_in_web() searchs for a song on the Internet and returns an instance of Song class that allows to download the song using Song.download_mp3()

>>> from song_downloader import search_in_web
>>> song = search_in_web(title='Loosing My Mind', artist='Falling In Reverse')
>>> print(song)
Song(Loosing My Mind, Falling In Reverse, 2018)
>>> song.download_mp3('[artist] — [title] ([year])')

The code will download the mp3 and save it as "Falling In Reverse — Loosing My Mind (2018).mp3".

You may ask: "why should I use strify if I could do that like *the code below*?"

>>> from song_downloader import search_in_web
>>> song = search_in_web(title='Loosing My Mind', artist='Falling In Reverse')
>>> print(song)
Song(Loosing My Mind, Falling In Reverse, 2018)
>>> song.download_mp3(f'{song.artist}{song.title} ({song.year})')

You definitely can. But there is no flexibility for the end user that can't change sources; usually, he/she just is not assumed to do that, it's not a good practice.

strify's approach is to ask a user to enter whatever pattern he/she wants and supply it to the program in any way (args, json, data base etc.) and just use the value in the script. That's the power: it's not necessary to define a pattern in the source code.

Now we can change the example and save it as download_mp3.py:

from song_downloader import search_in_web
args = parse_args()
song = search_in_web(title=args['title'], artist=args['artist'])
song.download_mp3(args['mp3_name_pattern'])

Then run it like this:

python3 download_mp3.py --title='loosing my mind' --artist='falling in reverse' --mp3-name-pattern='[artist] — [title] ([year])'

And check what's happened:

user@computer:~/$ ls -1
...
download_mp3.py
Falling In Reverse — Loosing My Mind (2018).mp3
...
user@computer:~/$

Usage Guide

Let's continue with our Song class:

from random import randrange

class Song:
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

    def get_year(self):
        year = randrange(1960, 2020)
        return year

Notice: Song.get_year() is a mock. In a real code it has to connect to the net and find all the data there using self.title and self.artist.

Diving in

First of all, you need to take a look at the terminology:

  • stringification — process of building class instance representation (according to a pattern)
  • pattern — a string with markers. After stringification all the markers will be replaced with marker values (or final_marker_value, see preprocessing function)
  • markerf'[{marker_name}]'
  • marker attribute — name of a class instance attribute which value is used during stringification.
  • marker value: the way strify gets marker value looks like this:
import inspect
def get_marker_value(class_instance, marker_attribute):
    marker_value = getattr(class_instance, marker_attribute)
    if inspect.ismethod(marker_value):
        marker_value = marker_value()
    return str(marker_value)
  • preprocessing function:
def preprocessing_function(marker_value: str) -> str:
    final_marker_value = whatever_magic_you_want(marker_value)
    return final_marker_value

Way #1: use stringifyable and StringifyInfo

stringifyable gets list of StringifyInfo and adds stringify(pattern: str) -> str method to the class it decorates.
One StringifyInfo in the list describes one marker.

Signature:
StringifyInfo(marker_name, marker_attribute=None, preprocessing_function=None)
If marker_attribute isn't passed to the constructor (i.e. is None) then it's assumed to be the same as passed marker_name.

from strify import stringifyable, StringifyInfo

from random import randrange

# transforms 'tHis striNg' into 'This String'
def format_proper_name(string):
    return ' '.join(word[0].upper() + word[1:].lower() for word in string.split(' '))

@stringifyable([
    StringifyInfo('title', None, format_proper_name),
    StringifyInfo('artist', 'artist', format_proper_name),
    StringifyInfo('year', 'get_year'),
])
class Song:
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

    def get_year(self):
        year = randrange(1960, 2020)
        return year

Now, the following is possible:

>>> song = Song('loosing my mind', 'falling in reverse')
>>> song.stringify('[artist] — [title] ([year])')
Falling In Reverse  Loosing My Mind (2018)
>>> song.stringify('[title]: [artist], [year]')
Loosing My Mind: Falling In Reverse, 2018

Way #2: use stringifyable and stringify

Signature:
stringify(preprocessing_func=None, marker_name=None)
If marker_name equals None (i.e. not passed) then it's assumed that marker_name == func.__name__

from strify import stringifyable, stringify

from random import randrange

# transforms 'tHis striNg' into 'This String'
def format_proper_name(string):
    return ' '.join(word[0].upper() + word[1:].lower() for word in string.split(' '))

@stringifyable()
class Song:
    def __init__(self, title, artist):
        self._title = title
        self._artist = artist

    @stringify(format_proper_name)
    def artist(self):
        return self._artist

    @stringify(format_proper_name)
    def title(self):
        return self._title

    @stringify(marker_name='year')
    def get_year(self):
        year = randrange(1960, 2020)
        return year

Notice: it would be more usual to make properties. In this case, stringify must be the first decorator that's applied to the function.

@property
@stringify(format_proper_name)
def title(self):
    return self._title

Now you can test the code we ran at the end of Way #1 and ensure that we achieved the same interface and results:

>>> song = Song('loosing my mind', 'falling in reverse')
>>> song.stringify('[artist] — [title] ([year])')
Falling In Reverse  Loosing My Mind (2018)
>>> song.stringify('[title]: [artist], [year]')
Loosing My Mind: Falling In Reverse, 2018

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for strify, version 1.0.1
Filename, size File type Python version Upload date Hashes
Filename, size strify-1.0.1-py3-none-any.whl (13.8 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size strify-1.0.1.tar.gz (9.9 kB) File type Source Python version None Upload date Hashes View

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page