Skip to main content

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)
  • marker — f'[{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 stringifiable and StringifyInfo

stringifiable 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 stringifiable, 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(' '))

@stringifiable([
    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 stringifiable 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 stringifiable, 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(' '))

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

Metadata

Release files for strify 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for strify 1.0.2
File Size Uploaded Provenance
strify-1.0.2.tar.gz 9.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for strify 1.0.2
File Interpreter ABI Platform
strify-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 23.8 kB

Release files / strify-1.0.2.tar.gz

Download URL strify-1.0.2.tar.gz
Size 9.9 kB
Tags Source
SHA-256 checksum
How to use checksums
79914a3d40916f8d8f2530c1254fc54b032e2c31c4c98de7fbbc6825b5cc5aa7
BLAKE2b-256 checksum
How to use checksums
f3d84c0a2876752fa8cb06fea544925efd58cab947c7834cdbdd306bd35dacd9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/45.1.0 requests-toolbelt/0.9.1 tqdm/4.41.1 CPython/3.6.9

Release files / strify-1.0.2-py3-none-any.whl

Download URL strify-1.0.2-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
31436d632605c7859e17435f532409dc889fb51866a69fc229aa3d367d4a8464
BLAKE2b-256 checksum
How to use checksums
09bff9a8fa864a7afaaf8e59c280d77b926e7973204cfe53ef535f0fc712096a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/45.1.0 requests-toolbelt/0.9.1 tqdm/4.41.1 CPython/3.6.9

Security for strify 1.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page