Stringable
Stringable
Section titled “Stringable”The Stringable class is a fluent, immutable wrapper around Python’s built-in str type. It provides a rich collection of chainable methods for common string operations — searching, replacing, transforming case, encoding, validating, and more — without mutating the original value. Because Stringable extends str, it can be used anywhere a regular string is expected while adding expressive, framework-level convenience.
Import
Section titled “Import”from orionis.support.strings.stringable import StringableCreating an Instance
Section titled “Creating an Instance”s = Stringable("Hello World")Every method that returns text produces a new Stringable instance, so you can chain calls fluently:
result = Stringable(" hello world ").trim().title().finish("!")# "Hello World!"Substring Extraction
Section titled “Substring Extraction”after / afterLast
Section titled “after / afterLast”Return the portion of the string after the first (or last) occurrence of a delimiter.
Stringable("foo/bar/baz").after("/") # "bar/baz"Stringable("foo/bar/baz").afterLast("/") # "baz"If the delimiter is not found, the original string is returned.
before / beforeLast
Section titled “before / beforeLast”Return the portion of the string before the first (or last) occurrence of a delimiter.
Stringable("foo/bar/baz").before("/") # "foo"Stringable("foo/bar/baz").beforeLast("/") # "foo/bar"between / betweenFirst
Section titled “between / betweenFirst”Extract the text between two delimiters. between uses the first occurrence of the start delimiter and the first occurrence of the end delimiter after it. betweenFirst behaves identically and is provided for readability.
Stringable("[hello]").between("[", "]") # "hello"Stringable("[a][b]").betweenFirst("[", "]") # "a"Returns an empty Stringable when either delimiter is missing.
substr
Section titled “substr”Return a substring starting at a given position, optionally limited to a specific length.
Stringable("Hello World").substr(6) # "World"Stringable("Hello World").substr(0, 5) # "Hello"Take characters from the start (positive) or end (negative) of the string.
Stringable("hello world").take(5) # "hello"Stringable("hello world").take(-5) # "world"excerpt
Section titled “excerpt”Extract an excerpt around the first occurrence of a phrase, with configurable radius and omission indicator.
text = Stringable("The quick brown fox jumps over the lazy dog")text.excerpt("fox", {"radius": 5, "omission": "..."})# "...brown fox jumps..."Returns None if the phrase is not found.
Search & Position
Section titled “Search & Position”contains
Section titled “contains”Check whether the string contains one or more substrings. Accepts a single string or an iterable, with an optional case-insensitive flag.
Stringable("Hello World").contains("World") # TrueStringable("Hello World").contains("world", ignore_case=True) # TrueStringable("Hello World").contains(["foo", "World"]) # TruecontainsAll
Section titled “containsAll”Return True only when every needle in the list is present.
Stringable("hello world foo").containsAll(["hello", "world", "foo"]) # TrueStringable("hello world").containsAll(["hello", "xyz"]) # FalsedoesntContain
Section titled “doesntContain”The inverse of contains.
Stringable("hello world").doesntContain("xyz") # TruestartsWith / endsWith
Section titled “startsWith / endsWith”Stringable("hello.py").endsWith(".py") # TrueStringable("hello.py").endsWith([".py", ".txt"]) # TrueStringable("hello world").startsWith("hello") # TruedoesntStartWith / doesntEndWith
Section titled “doesntStartWith / doesntEndWith”Stringable("hello").doesntStartWith("world") # TrueStringable("hello.py").doesntEndWith(".txt") # Trueexactly
Section titled “exactly”Strict equality comparison.
Stringable("hello").exactly("hello") # TrueStringable("hello").exactly("Hello") # Falseposition
Section titled “position”Find the index of the first occurrence of a substring, optionally starting from an offset. Returns False when not found.
Stringable("hello world").position("world") # 6Stringable("hello world").position("xyz") # Falsematch / matchAll / isMatch / test
Section titled “match / matchAll / isMatch / test”Regular-expression helpers:
Stringable("order-1234").match(r"\d+") # "1234"Stringable("a1b2c3").matchAll(r"\d") # ["1", "2", "3"]Stringable("hello").isMatch(r"^h.*o$") # TrueStringable("hello").test(r"^h.*o$") # True (alias)isPattern
Section titled “isPattern”Wildcard-based pattern matching (* and ?), with optional case-insensitive mode.
Stringable("hello world").isPattern("hello*") # TrueStringable("Hello World").isPattern("hello*", ignore_case=True) # TrueReplacement
Section titled “Replacement”replace
Section titled “replace”Replace substrings with new values. Supports parallel lists for multi-replace and a case-insensitive mode.
Stringable("Hello World").replace("World", "Python") # "Hello Python"Stringable("Hello World").replace("world", "Python", case_sensitive=False) # "Hello Python"Stringable("a b c").replace(["a", "b"], ["x", "y"]) # "x y c"replaceFirst / replaceLast
Section titled “replaceFirst / replaceLast”Replace only the first or last occurrence.
Stringable("aaa").replaceFirst("a", "b") # "baa"Stringable("aaa").replaceLast("a", "b") # "aab"replaceStart / replaceEnd
Section titled “replaceStart / replaceEnd”Replace a substring only if it appears as a prefix or suffix.
Stringable("helloWorld").replaceStart("hello", "hi") # "hiWorld"Stringable("helloWorld").replaceEnd("World", "Python") # "helloPython"replaceArray
Section titled “replaceArray”Replace occurrences of a search string one at a time with successive elements from a list.
Stringable("? ? ?").replaceArray("?", ["a", "b", "c"]) # "a b c"replaceMatches
Section titled “replaceMatches”Regex-powered replacement with a string or callable.
Stringable("hello123world").replaceMatches(r"\d+", "NUM")# "helloNUMworld"
Stringable("hello").replaceMatches(r"[aeiou]", lambda m: m.group(0).upper())# "hEllO"remove
Section titled “remove”Remove substrings entirely.
Stringable("hello world").remove("l") # "heo word"Stringable("Hello World").remove("hello", case_sensitive=False) # " World"Stringable("hello world").remove(["hello", " "]) # "world"Replace multiple keywords using a dictionary mapping.
Stringable("I love cats").swap({"cats": "dogs"}) # "I love dogs"Case Conversion
Section titled “Case Conversion”lower / upper
Section titled “lower / upper”Stringable("HELLO World").lower() # "hello world"Stringable("hello World").upper() # "HELLO WORLD"swapCase
Section titled “swapCase”Invert the case of every character.
Stringable("Hello World").swapCase() # "hELLO wORLD"camel / kebab / snake / studly / pascal
Section titled “camel / kebab / snake / studly / pascal”Convert between common naming conventions.
Stringable("hello_world").camel() # "helloWorld"Stringable("helloWorld").kebab() # "hello-world"Stringable("helloWorld").snake() # "hello_world"Stringable("helloWorld").snake(".") # "hello.world"Stringable("hello_world").studly() # "HelloWorld"Stringable("hello_world").pascal() # "HelloWorld" (alias of studly)title / headline / apa
Section titled “title / headline / apa”Stringable("hello world").title() # "Hello World"Stringable("hello world").headline() # "Hello World"
Stringable("the quick brown fox").apa()# "The Quick Brown Fox" — APA-style: short words lowercased except first/lastucfirst / lcfirst
Section titled “ucfirst / lcfirst”Capitalize or lowercase only the first character.
Stringable("hello world").ucfirst() # "Hello world"Stringable("Hello World").lcfirst() # "hello World"convertCase
Section titled “convertCase”Numeric mode-based conversion:
| Mode | Effect |
|---|---|
None / 0 | casefold |
1 | UPPER |
2 | lower |
3 | Title |
Stringable("HELLO").convertCase(2) # "hello"Generate a URL-friendly slug. Supports a custom separator and a character-replacement dictionary.
Stringable("Hello World!").slug() # "hello-world"Stringable("Hello World").slug("_") # "hello_world"Stringable("user@example").slug() # "user-at-example"Trimming & Padding
Section titled “Trimming & Padding”trim / ltrim / rtrim
Section titled “trim / ltrim / rtrim”Stringable(" hello ").trim() # "hello"Stringable("--hello--").trim("-") # "hello"Stringable(" hello ").ltrim() # "hello "Stringable(" hello ").rtrim() # " hello"lStrip / rStrip
Section titled “lStrip / rStrip”Python-style lstrip / rstrip wrappers returning a Stringable.
Stringable("xxhello").lStrip("x") # "hello"Stringable("helloxx").rStrip("x") # "hello"padBoth / padLeft / padRight
Section titled “padBoth / padLeft / padRight”Pad the string to reach a desired total length.
Stringable("hi").padBoth(6) # " hi "Stringable("hi").padLeft(5) # " hi"Stringable("5").padLeft(3, "0") # "005"Stringable("hi").padRight(5) # "hi "Pad with leading zeros.
Stringable("42").zFill(5) # "00042"squish
Section titled “squish”Collapse consecutive whitespace into a single space and trim.
Stringable(" hello world ").squish() # "hello world"deduplicate
Section titled “deduplicate”Collapse consecutive occurrences of a character into one.
Stringable("hello world").deduplicate() # "hello world"Stringable("aabbcc").deduplicate("b") # "aabcc"Building & Wrapping
Section titled “Building & Wrapping”append / prepend
Section titled “append / prepend”Stringable("hello").append(" world", "!") # "hello world!"Stringable("world").prepend("hello ", "dear ") # "hello dear world"newLine
Section titled “newLine”Append one or more newline characters.
Stringable("hello").newLine() # "hello\n"Stringable("hello").newLine(2) # "hello\n\n"finish / start
Section titled “finish / start”Ensure the string ends (or starts) with a given value — does not duplicate it if already present.
Stringable("path/to").finish("/") # "path/to/"Stringable("path/to/").finish("/") # "path/to/"Stringable("world").start("hello ") # "hello world"wrap / unwrap
Section titled “wrap / unwrap”Stringable("hello").wrap('"') # '"hello"'Stringable("hello").wrap("[", "]") # "[hello]"Stringable('"hello"').unwrap('"') # "hello"Stringable("[hello]").unwrap("[", "]") # "hello"repeat
Section titled “repeat”Stringable("ab").repeat(3) # "ababab"Stringable("ab").repeat(0) # ""reverse
Section titled “reverse”Stringable("hello").reverse() # "olleh"Limiting & Truncation
Section titled “Limiting & Truncation”Truncate the string to a maximum number of characters. An optional preserve_words flag avoids cutting in the middle of a word.
Stringable("hello world").limit(5) # "hello..."Stringable("hello world").limit(5, " [more]") # "hello [more]"Stringable("hello world foo").limit(8, "...", preserve_words=True) # word-safe truncationLimit to a maximum number of words.
Stringable("one two three four").words(2) # "one two..."Mask a portion of the string with a repeated character.
Stringable("password").mask("*", 2, 4) # "pa****rd"Stringable("hello").mask("*", 2) # "he***"Stringable("hello").mask("*", -3) # "he***"Splitting
Section titled “Splitting”explode
Section titled “explode”Split by a literal delimiter.
Stringable("a,b,c").explode(",") # ["a", "b", "c"]Split by a regular expression or by chunk length.
Stringable("a1b2c3").split(r"\d") # ["a", "b", "c", ""]Stringable("abcdef").split(2) # ["ab", "cd", "ef"]ucsplit
Section titled “ucsplit”Split at uppercase boundaries.
Stringable("helloWorld").ucsplit() # ["hello", "World"]Counting
Section titled “Counting”length
Section titled “length”Stringable("hello").length() # 5wordCount
Section titled “wordCount”Stringable("hello world").wordCount() # 2substrCount
Section titled “substrCount”Count non-overlapping occurrences of a substring, optionally within an offset/length window.
Stringable("banana").substrCount("an") # 2Word Wrapping
Section titled “Word Wrapping”wordWrap
Section titled “wordWrap”Wrap text to a specified line width.
long_text = Stringable("This is a long sentence that needs wrapping")long_text.wordWrap(20)Encoding & Hashing
Section titled “Encoding & Hashing”toBase64 / fromBase64
Section titled “toBase64 / fromBase64”Stringable("hello").toBase64() # "aGVsbG8="Stringable("aGVsbG8=").fromBase64() # "hello"fromBase64 accepts a keyword-only strict parameter. When strict=True, invalid input raises a RuntimeError instead of returning an empty string.
md5 / sha1 / sha256
Section titled “md5 / sha1 / sha256”Convenience hashing methods returning hexadecimal digest strings.
Stringable("hello").md5() # 32-char hex digestStringable("hello").sha1() # 40-char hex digestStringable("hello").sha256() # 64-char hex digestHash with any algorithm supported by hashlib.
Stringable("hello").hash("sha256") # equivalent to .sha256()toHtmlString / stripTags
Section titled “toHtmlString / stripTags”Stringable("<b>Hello</b>").toHtmlString() # "<b>Hello</b>"Stringable("<p>Hello <b>World</b></p>").stripTags() # "Hello World"ascii / transliterate
Section titled “ascii / transliterate”Remove or replace non-ASCII characters.
Stringable("café").ascii() # "cafe"Stringable("café").transliterate("?", strict=True) # "caf?"encrypt / decrypt
Section titled “encrypt / decrypt”Encrypt and decrypt via the framework’s Crypt facade. Requires the Encrypter service to be registered.
encrypted = Stringable("secret").encrypt()decrypted = encrypted.decrypt()Type Conversion
Section titled “Type Conversion”toInteger / toFloat / toBoolean
Section titled “toInteger / toFloat / toBoolean”Stringable("42").toInteger() # 42Stringable("0xff").toInteger(16) # 255Stringable("3.14").toFloat() # 3.14Stringable("true").toBoolean() # TrueStringable("0").toBoolean() # FalseReturn the underlying plain str.
Stringable("hello").value() # "hello" (type: str)jsonSerialize
Section titled “jsonSerialize”Return a plain str suitable for JSON encoding.
Stringable("hello").jsonSerialize() # "hello"Validation
Section titled “Validation”isEmpty / isNotEmpty
Section titled “isEmpty / isNotEmpty”Stringable("").isEmpty() # TrueStringable("hello").isNotEmpty() # TrueCharacter Type Checks
Section titled “Character Type Checks”| Method | Returns True when… |
|---|---|
isAlnum() | All characters are alphanumeric |
isAlpha() | All characters are alphabetic |
isDecimal() | All characters are decimal digits |
isDigit() | All characters are digit characters |
isIdentifier() | Valid Python identifier |
isLower() | All cased characters are lowercase |
isUpper() | All cased characters are uppercase |
isNumeric() | All characters are numeric |
isPrintable() | All characters are printable |
isSpace() | Only whitespace characters |
isTitle() | Titlecased string |
isAscii() | Only 7-bit ASCII characters |
isJson
Section titled “isJson”Stringable('{"key": "value"}').isJson() # TrueStringable("not json").isJson() # FalseValidate a URL, optionally restricting to specific protocols.
Stringable("https://example.com").isUrl() # TrueStringable("ftp://example.com").isUrl(protocols=["ftp"]) # TrueisUuid / isUlid
Section titled “isUuid / isUlid”Stringable("550e8400-e29b-41d4-a716-446655440000").isUuid() # TrueStringable("550e8400-e29b-41d4-a716-446655440000").isUuid(4) # True (version 4)Stringable("01ARZ3NDEKTSV4RRFFQ69G5FAV").isUlid() # TruePluralization
Section titled “Pluralization”plural / singular
Section titled “plural / singular”Basic English pluralization rules.
Stringable("cat").plural() # "cats"Stringable("cat").plural(1) # "cat"Stringable("baby").plural() # "babies"Stringable("bus").plural() # "buses"Stringable("cat").plural(3, prepend_count=True) # "3 cats"
Stringable("cats").singular() # "cat"Stringable("babies").singular() # "baby"pluralStudly / pluralPascal
Section titled “pluralStudly / pluralPascal”Pluralize the last word of a StudlyCase or PascalCase string.
Stringable("BlogPost").pluralStudly() # "BlogPosts"Stringable("UserProfile").pluralPascal() # "UserProfiles"Callback Parsing
Section titled “Callback Parsing”parseCallback
Section titled “parseCallback”Parse a Class@method style string into its components.
Stringable("MyClass@myMethod").parseCallback() # ["MyClass", "myMethod"]Stringable("MyClass").parseCallback("handle") # ["MyClass", "handle"]Stringable("MyClass").parseCallback() # ["MyClass", None]Path Helpers
Section titled “Path Helpers”basename / dirname
Section titled “basename / dirname”Stringable("/home/user/file.txt").basename() # "file.txt"Stringable("/home/user/file.txt").basename(".txt") # "file"Stringable("/home/user/file.txt").dirname() # "/home/user"Offset Access
Section titled “Offset Access”offsetExists / offsetGet / charAt
Section titled “offsetExists / offsetGet / charAt”Stringable("hello").offsetExists(2) # TrueStringable("hello").offsetExists(99) # FalseStringable("hello").offsetGet(1) # "e"Stringable("hello").charAt(0) # "h"Stringable("hello").charAt(99) # FalseStringable also supports standard indexing and slicing:
Stringable("hello")[1] # Stringable("e")Stringable("hello")[1:4] # Stringable("ell")Numeric Extraction
Section titled “Numeric Extraction”numbers
Section titled “numbers”Remove all non-numeric characters.
Stringable("phone: +1-234-567").numbers() # "1234567"Extract values using a simplified sscanf-style format string with %s, %d and %f placeholders.
Stringable("John 30 5.9").scan("%s %d %f") # ["John", "30", "5.9"]Substring Replacement
Section titled “Substring Replacement”substrReplace
Section titled “substrReplace”Replace text within a specific range of the string using offset and length.
Stringable("hello world").substrReplace("Python", 6, 5) # "hello Python"Conditional Execution
Section titled “Conditional Execution”All when* methods accept a callback and an optional default. The callback receives the current Stringable and its return value becomes the new string. If the condition is False and no default is provided, the original string is returned unchanged.
Execute a callback conditionally based on a boolean or callable condition.
Stringable("hello").when(True, lambda s: s.upper()) # "HELLO"Stringable("hello").when(False, lambda s: s.upper()) # "hello"Stringable("hello").when(lambda s: s.isNotEmpty(), lambda s: s.upper()) # "HELLO"whenContains / whenContainsAll
Section titled “whenContains / whenContainsAll”Stringable("hello world").whenContains("world", lambda s: s.upper())# "HELLO WORLD"whenEmpty / whenNotEmpty
Section titled “whenEmpty / whenNotEmpty”Stringable("").whenEmpty(lambda s: Stringable("default")) # "default"Stringable("hello").whenNotEmpty(lambda s: s.upper()) # "HELLO"whenStartsWith / whenDoesntStartWith
Section titled “whenStartsWith / whenDoesntStartWith”Stringable("hello world").whenStartsWith("hello", lambda s: s.upper())# "HELLO WORLD"whenEndsWith / whenDoesntEndWith
Section titled “whenEndsWith / whenDoesntEndWith”Stringable("hello.py").whenEndsWith(".py", lambda s: s.upper())# "HELLO.PY"whenExactly / whenNotExactly
Section titled “whenExactly / whenNotExactly”Stringable("hello").whenExactly("hello", lambda s: s.upper()) # "HELLO"Stringable("hello").whenNotExactly("world", lambda s: s.upper()) # "HELLO"whenTest / whenIs / whenIsAscii / whenIsUuid / whenIsUlid
Section titled “whenTest / whenIs / whenIsAscii / whenIsUuid / whenIsUlid”Stringable("hello123").whenTest(r"\d+", lambda s: s.upper()) # "HELLO123"Pipelines & Side Effects
Section titled “Pipelines & Side Effects”Pass the string through a callback and return the result.
Stringable("hello").pipe(lambda s: s.upper()) # "HELLO"Execute a side-effect callback without modifying the string. Useful for logging or debugging within a chain.
Stringable("hello").tap(lambda s: print(s)).upper() # prints "hello", returns "HELLO"Date Parsing
Section titled “Date Parsing”toDate
Section titled “toDate”Parse the string into a datetime object using the specified format (default %Y-%m-%d). The resulting datetime is timezone-aware using the framework’s local timezone.
Stringable("2026-04-01").toDate() # datetime(2026, 4, 1, ...)Stringable("01/04/2026").toDate("%d/%m/%Y") # datetime(2026, 4, 1, ...)Raises ValueError if the string does not match the format.