{"id":16886746,"url":"https://github.com/jlevy/strif","last_synced_at":"2025-04-09T07:07:41.266Z","repository":{"id":57471823,"uuid":"42476068","full_name":"jlevy/strif","owner":"jlevy","description":"Tiny, useful Python lib for strings and files","archived":false,"fork":false,"pushed_at":"2025-03-19T03:52:40.000Z","size":108,"stargazers_count":42,"open_issues_count":5,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-02T03:22:44.923Z","etag":null,"topics":["files","library","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jlevy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-09-14T20:46:23.000Z","updated_at":"2025-03-19T18:48:06.000Z","dependencies_parsed_at":"2024-10-26T21:17:39.071Z","dependency_job_id":"88725787-3c0a-41b1-9997-055cf70478f1","html_url":"https://github.com/jlevy/strif","commit_stats":{"total_commits":13,"total_committers":1,"mean_commits":13.0,"dds":0.0,"last_synced_commit":"68e1a3ba515036ab1783e10611f1cfc3be9a6907"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fstrif","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fstrif/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fstrif/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fstrif/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlevy","download_url":"https://codeload.github.com/jlevy/strif/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247994121,"owners_count":21030050,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["files","library","python"],"created_at":"2024-10-13T16:41:08.840Z","updated_at":"2025-04-09T07:07:41.242Z","avatar_url":"https://github.com/jlevy.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# strif\n\nStrif is a tiny (\u003c1000 loc) library of string and file utilities for modern Python.\n\nIt’s an assembly of some functions and tricks that have repeatedly shown value in\nvarious projects. The goal is to complement the standard libs, not replace or wrap them.\n\n✨ **NEW:** **Version 2.0** is now updated for Python 3.10-3.13! ✨\n\n## Highlights\n\nUse `pydoc strif` for full docs!\nA quick overview is below.\n\n### Base36 Identifiers\n\nSeveral functions offer [base 36](https://en.wikipedia.org/wiki/Base36) identifiers.\n\nIt’s frequently preferable to use base 36. Base 36 is briefer than hex, avoids ugly\nnon-alphanumeric characters like base 64, and is case insensitive, which is generally\nwise (e.g. due to MacOS case-insensitive filesystems).\n\n### Text Abbreviations and Formatting\n\n- **`abbrev_str(string: str, max_len: Optional[int] = 80, indicator: str = '…')`**\n\n  Abbreviates a string and appends an indicator if the content exceeds the allowed\n  length.\n\n- **`abbrev_list(items: List[Any], max_items: int = 10, item_max_len: Optional[int] =\n  40, joiner: str = ', ', indicator: str = '…')`**\n\n  Shortens each element of a list and appends an ellipsis if the list is truncated.\n\n- **`single_line(text: str)`**\n\n  Converts multi-line text into a single line by replacing extra whitespace with spaces.\n\n- **`quote_if_needed(arg: Any)`**\n\n  Returns a string with quotes if needed for proper display (for example, for filenames\n  with spaces).\n\n### String Identifiers, Timestamps, and Hashing\n\n- **`new_uid(bits: int = 64)`**\n\n  Generates a random base36 alphanumeric string with at least the specified bits of\n  randomness. Suitable for filenames (especially on case-insensitive filesystems).\n\n- **`new_timestamped_uid(bits: int = 32)`**\n\n  Creates a unique ID starting with an ISO timestamp, then fractions of seconds and bits\n  of randomness. *Example*: `20150912T084555Z-378465-43vtwbx`\n\n- **`iso_timestamp(microseconds: bool = True)`**\n\n  Returns an ISO 8601 timestamp in UTC, e.g. `2015-09-12T08:41:12.397217Z` (with\n  microseconds) or `2015-09-12T08:41:12Z` (without).\n\n- **`format_iso_timestamp(datetime_obj: datetime, microseconds: bool = True)`**\n\n  Formats a given datetime object as an ISO 8601 timestamp, ensuring UTC formatting with\n  a trailing Z.\n\n- **`clean_alphanum(string: str, max_length: Optional[int] = None)`**\n\n  Converts a string to a clean identifier by keeping only the first alphanumeric\n  characters and replacing others with underscores.\n\n- **`clean_alphanum_hash(string: str, max_length: int = 64, max_hash_len: Optional[int]\n  = None)`**\n\n  Combines the cleaned version of a string with a base36 SHA1 hash to minimize\n  collisions.\n\n### File Hashing\n\n- **`file_mtime_hash(path: str | Path)`**\n\n  Computes a fast hash using a file's name, size, and high-resolution modification time,\n  without looking at file contents.\n  A useful key for fast caching of file contents.\n\n- **`hash_string(string: str, algorithm: str = 'sha1') -\u003e Hash`** and\n  **`hash_file(file_path: str | Path, algorithm: str = 'sha1') -\u003e Hash`**\n\n  Provide flexible hashing mechanisms.\n  The returned `Hash` object has properties to output the digest in hexadecimal, base36,\n  or with a prefixed algorithm name.\n\n### Atomic File Operations with Optional Backups\n\n- **`atomic_output_file(dest_path: str | Path, make_parents: bool = False,\n  backup_suffix: Optional[str] = None, tmp_suffix: str = '.partial')`**\n\n  A context manager for writing files or directories atomically.\n  A temporary file is created and, upon successful completion, renamed to the target\n  location.\n\n- **`copyfile_atomic(source_path: str | Path, dest_path: str | Path, make_parents: bool\n  = False, backup_suffix: Optional[str] = None)`**\n\n  Atomically copies a file while preserving its timestamps.\n\n- **`copytree_atomic(source_path: str | Path, dest_path: str | Path, make_parents: bool\n  = False, backup_suffix: Optional[str] = None, symlinks: bool = False)`**\n\n  Recursively copies a directory or file atomically.\n\n- **`move_to_backup(path: str | Path, backup_suffix: str = '{timestamp}.bak')`** and\n  **`copy_to_backup(path: str | Path, backup_suffix: str = '{timestamp}.bak')`**\n\n  Functions to move or copy an existing file or directory to a backup destination.\n\n- **`move_file(src_path: Path, dest_path: Path, keep_backup: bool = True, backup_suffix:\n  str = '{timestamp}.bak')`**\n\n  Moves a file to a new location, automatically creating parent directories and\n  optionally keeping a backup of the destination if it already exists.\n\nIt’s generally good practice when creating files to write to a file with a temporary\nname, and move it to a final location once the file is complete.\nThis way, you never leave partial, incorrect versions of files in a directory due to\ninterruptions or failures.\n\nFor example, these can (and in most cases should) be used in place of `shutil.copyfile`\nor `shutil.copytree`:\n\n```python\ncopyfile_atomic(source_path, dest_path, make_parents=True, backup_suffix=None)\n```\n\nYou also have convenience options for creating parent directories of the target, if they\ndon't exist. And you can keep a backup of the target, rather than clobber it, if you\nprefer. Used judiciously, these options can save you some boilerplate coding.\n\nIt’s helpful to have syntax sugar for creating files or directories atomically:\n\n```python\nwith atomic_output_file(\"some-dir/my-final-output.txt\") as temp_target:\n    with open(temp_target, \"w\") as f:\n        f.write(\"some contents\")\n```\n\nNow if there is some issue during write, the output will instead be at a temporary\nlocation in the same directory (with a name like\n`some-dir/my-final-output.txt.partial.XXXXX`.) This ensures integrity of the file\nappearing in the final location.\n\nThere are also some handy additional options:\n\n```python\nwith atomic_output_file(\"some-dir/my-final-output.txt\",\n                        make_parents=True, backup_suffix=\".old.{timestamp}\") as temp_target:\n    with open(temp_target, \"w\") as f:\n        sf.write(\"some contents\")\n```\n\nThis creates parent folders as needed (a major convenience).\nAnd if you would have clobbered a previous output, it keeps a backup with a (fixed or\nuniquely timestamped) suffix.\n\n### Syntax Sugar for Temporary Files\n\nSyntax sugar for auto-deleting temporary files or directories using `with`:\n\n```python\nwith temp_output_file(\"my-scratch.\") as (fd, path):\n    # Do a bunch of stuff with the opened file descriptor or path, knowing\n    # it will be removed assuming successful termination.\n\n\nwith temp_output_dir(\"work-dir.\", dir=\"/var/tmp\") as work_dir:\n    # Create some files in the now-existing path work_dir, and it will be\n    # deleted afterwards.\n```\n\nNote these don’t delete files in case of error, which is usually what you want.\nAdd `always_clean=True` if you want the temporary file or directory to be removed no\nmatter what.\n\n## FAQ\n\n### Why bother, if it’s so short?\n\nBecause it saves time, saves you stupid bugs and clumsy repetition, and has zero (yes\nzero) dependencies.\n\n### Is it mature?\n\nI’ve used many of these functions in production situations for years.\nBut it doesn't have comprehensive tests at the moment.\n\n## Installation\n\n```sh\n# Use pip\npip install strif\n# Or poetry\npoetry add strif\n```\n\n## Development\n\nFor development workflows, see [development.md](development.md).\n\n* * *\n\n*This project was built from\n[simple-modern-uv](https://github.com/jlevy/simple-modern-uv).*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlevy%2Fstrif","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlevy%2Fstrif","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlevy%2Fstrif/lists"}