Claude Code for grp: Python Unix Group Database — Claude Skills 360 Blog
Blog / AI / Claude Code for grp: Python Unix Group Database
AI

Claude Code for grp: Python Unix Group Database

Published: October 22, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s grp module reads the Unix group database (/etc/group), returning struct_group named tuples. import grp. getgrnam: grp.getgrnam(name)struct_group; raises KeyError if not found. getgrgid: grp.getgrgid(gid)struct_group. getgrall: grp.getgrall()list[struct_group]. struct_group fields: gr_name (str), gr_passwd (str, usually "x"), gr_gid (int), gr_mem (list of member username strings). Current process GID: os.getgid() / os.getegid(). All group IDs for a user: os.getgrouplist(user, gid) (returns list of ints). Add supplementary group: requires root — os.setgroups(gids). Check membership: username in grp.getgrnam("docker").gr_mem. Windows: not available. Claude Code generates group membership validators, permission checkers, privileged-group guards, and user-group audit reporters.

CLAUDE.md for grp

## grp Stack
- Stdlib: import grp, os
- By name: entry = grp.getgrnam("sudo")        # KeyError if not found
- By GID:  entry = grp.getgrgid(0)             # root group
- All:     entries = grp.getgrall()
- Fields:  .gr_name  .gr_passwd  .gr_gid  .gr_mem (list of usernames)
- Current: os.getgid()  os.getegid()
- Members: os.getgrouplist(username, primary_gid)  # returns [int, ...]

grp Unix Group Pipeline

# app/grputil.py — group lookup, membership, audit, privilege guard
from __future__ import annotations

import os
import platform
from dataclasses import dataclass
from typing import Iterator

_GRP_AVAILABLE = platform.system() != "Windows"
if _GRP_AVAILABLE:
    import grp


# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic lookups
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class GroupEntry:
    name:    str
    gid:     int
    members: list[str]

    def __str__(self) -> str:
        members = ", ".join(self.members) if self.members else "(none)"
        return f"gid={self.gid:5d}  {self.name}  [{members}]"


def lookup_by_name(name: str) -> GroupEntry | None:
    """
    Look up a group by name. Returns None if not found.

    Example:
        g = lookup_by_name("docker")
        if g: print(g.members)
    """
    if not _GRP_AVAILABLE:
        return None
    try:
        e = grp.getgrnam(name)
        return GroupEntry(name=e.gr_name, gid=e.gr_gid, members=list(e.gr_mem))
    except KeyError:
        return None


def lookup_by_gid(gid: int) -> GroupEntry | None:
    """
    Look up a group by GID. Returns None if not found.

    Example:
        g = lookup_by_gid(os.getgid())
        if g: print(g.name)
    """
    if not _GRP_AVAILABLE:
        return None
    try:
        e = grp.getgrgid(gid)
        return GroupEntry(name=e.gr_name, gid=e.gr_gid, members=list(e.gr_mem))
    except KeyError:
        return None


def all_groups() -> list[GroupEntry]:
    """
    Return all group entries from the system database.

    Example:
        for g in all_groups():
            if g.members: print(g)
    """
    if not _GRP_AVAILABLE:
        return []
    return [
        GroupEntry(name=e.gr_name, gid=e.gr_gid, members=list(e.gr_mem))
        for e in grp.getgrall()
    ]


# ─────────────────────────────────────────────────────────────────────────────
# 2. Membership checks
# ─────────────────────────────────────────────────────────────────────────────

def user_in_group(username: str, group_name: str) -> bool:
    """
    Return True if username is listed as a member of group_name in /etc/group.
    Does NOT check primary group membership (see user_groups() for that).

    Example:
        if user_in_group("alice", "docker"):
            print("alice can use Docker")
    """
    if not _GRP_AVAILABLE:
        return False
    try:
        return username in grp.getgrnam(group_name).gr_mem
    except KeyError:
        return False


def user_groups(username: str, primary_gid: int | None = None) -> list[GroupEntry]:
    """
    Return all groups a user belongs to, including their primary group.
    If primary_gid is None, tries to look it up from the pwd module.

    Example:
        import pwd
        pw = pwd.getpwnam("alice")
        for g in user_groups("alice", pw.pw_gid):
            print(g)
    """
    if not _GRP_AVAILABLE:
        return []

    # Determine primary GID
    if primary_gid is None:
        try:
            import pwd as _pwd
            primary_gid = _pwd.getpwnam(username).pw_gid
        except (ImportError, KeyError):
            primary_gid = os.getgid()

    gids = os.getgrouplist(username, primary_gid)
    results: list[GroupEntry] = []
    for gid in gids:
        g = lookup_by_gid(gid)
        if g:
            results.append(g)
    return results


def current_process_groups() -> list[GroupEntry]:
    """
    Return all groups the current process belongs to.

    Example:
        for g in current_process_groups():
            print(g)
    """
    if not _GRP_AVAILABLE:
        return []
    results = []
    for gid in os.getgroups():
        g = lookup_by_gid(gid)
        if g:
            results.append(g)
    return results


def process_in_group(group_name: str) -> bool:
    """
    Return True if the current process has the named group in its supplementary
    group set (including effective GID).

    Example:
        if process_in_group("sudo"):
            print("process has sudo group access")
    """
    if not _GRP_AVAILABLE:
        return False
    try:
        target_gid = grp.getgrnam(group_name).gr_gid
    except KeyError:
        return False
    return target_gid in (os.getegid(), *os.getgroups())


# ─────────────────────────────────────────────────────────────────────────────
# 3. Privilege guard
# ─────────────────────────────────────────────────────────────────────────────

class InsufficientGroupError(PermissionError):
    """Raised when the process lacks the required group membership."""
    pass


def require_group(group_name: str) -> None:
    """
    Assert the current process belongs to group_name.
    Raises InsufficientGroupError if not.

    Example:
        require_group("docker")
        # ... code that needs docker group
    """
    if not process_in_group(group_name):
        g = lookup_by_name(group_name)
        gid_str = f" (gid={g.gid})" if g else " (group not found)"
        raise InsufficientGroupError(
            f"Process must be a member of group '{group_name}'{gid_str}. "
            f"Current groups: {[g.name for g in current_process_groups()]}"
        )


def require_root() -> None:
    """
    Assert the current process is running as root (UID 0 or GID 0).
    Raises PermissionError if not.

    Example:
        require_root()
        # privileged operation
    """
    if os.geteuid() != 0:
        raise PermissionError(
            f"Operation requires root (UID 0). Current UID: {os.geteuid()}"
        )


# ─────────────────────────────────────────────────────────────────────────────
# 4. Group audit
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class GroupAudit:
    total_groups:        int
    groups_with_members: int
    largest_group:       GroupEntry | None
    empty_groups:        list[GroupEntry]
    privileged_groups:   list[GroupEntry]   # GID < 100

    def __str__(self) -> str:
        largest = self.largest_group.name if self.largest_group else "—"
        return (
            f"total={self.total_groups}  "
            f"with_members={self.groups_with_members}  "
            f"empty={len(self.empty_groups)}  "
            f"privileged={len(self.privileged_groups)}  "
            f"largest={largest}"
        )


def audit_groups() -> GroupAudit:
    """
    Produce a summary audit of the system group database.

    Example:
        audit = audit_groups()
        print(audit)
        for g in audit.privileged_groups:
            print(f"  {g}")
    """
    groups = all_groups()
    if not groups:
        return GroupAudit(0, 0, None, [], [])

    with_members = [g for g in groups if g.members]
    empty = [g for g in groups if not g.members]
    privileged = [g for g in groups if g.gid < 100]
    largest = max(with_members, key=lambda g: len(g.members)) if with_members else None

    return GroupAudit(
        total_groups=len(groups),
        groups_with_members=len(with_members),
        largest_group=largest,
        empty_groups=empty,
        privileged_groups=privileged,
    )


def find_groups_for_user_pattern(username_pattern: str) -> list[tuple[GroupEntry, list[str]]]:
    """
    Find all groups that have members matching a simple substring pattern.
    Returns list of (GroupEntry, matching_members).

    Example:
        for g, members in find_groups_for_user_pattern("alice"):
            print(g.name, members)
    """
    results = []
    for g in all_groups():
        matching = [m for m in g.members if username_pattern in m]
        if matching:
            results.append((g, matching))
    return results


def groups_by_gid_range(low: int, high: int) -> list[GroupEntry]:
    """
    Return groups whose GID falls in [low, high] (inclusive).

    Example:
        system_groups = groups_by_gid_range(0, 999)
    """
    return [g for g in all_groups() if low <= g.gid <= high]


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== grp demo ===")

    if not _GRP_AVAILABLE:
        print("  grp not available on Windows — skipping")
        raise SystemExit(0)

    # ── current process groups ─────────────────────────────────────────────────
    print("\n--- current process groups ---")
    proc_groups = current_process_groups()
    for g in proc_groups[:5]:
        print(f"  {g}")
    if len(proc_groups) > 5:
        print(f"  ... and {len(proc_groups) - 5} more")

    # ── lookup by GID ──────────────────────────────────────────────────────────
    print("\n--- lookup by GID ---")
    for gid in [0, os.getgid()]:
        g = lookup_by_gid(gid)
        print(f"  gid={gid}: {g}")

    # ── lookup by name ─────────────────────────────────────────────────────────
    print("\n--- lookup by name ---")
    for name in ["root", "nogroup", "nobody"]:
        g = lookup_by_name(name)
        print(f"  {name!r}: {'found — ' + str(g) if g else 'not found'}")

    # ── process_in_group ───────────────────────────────────────────────────────
    print("\n--- process_in_group ---")
    for name in ["root", "docker", "sudo"]:
        print(f"  in '{name}': {process_in_group(name)}")

    # ── audit ──────────────────────────────────────────────────────────────────
    print("\n--- audit_groups ---")
    audit = audit_groups()
    print(f"  {audit}")
    print(f"  privileged group names: {[g.name for g in audit.privileged_groups[:8]]}")

    # ── GID range ─────────────────────────────────────────────────────────────
    print("\n--- system groups (GID 0–99) ---")
    sys_groups = groups_by_gid_range(0, 99)
    for g in sys_groups[:8]:
        print(f"  {g}")
    if len(sys_groups) > 8:
        print(f"  ... and {len(sys_groups) - 8} more")

    print("\n=== done ===")

For the pwd alternative — pwd.getpwnam(name) and pwd.getpwuid(uid) query the user database (/etc/passwd) and return struct_passwd entries with pw_name, pw_uid, pw_gid, pw_dir, and pw_shell — use pwd when you need user-level identity (UID, home directory, login shell); use grp when you need group-level authority (GID, group membership list, supplementary groups). The two modules are complementary: grp.getgrall() enumerates groups while os.getgrouplist(username, primary_gid) bridges from a user to their group IDs. For the os alternative — os.getgroups() returns the list of supplementary GIDs for the current process, and os.getgid()/os.getegid() return primary/effective GID — use os.getgroups() when you only need GID integers for permission checks; use grp when you need the group name string or the full member list from the group database. The Claude Skills 360 bundle includes grp skill sets covering lookup_by_name()/lookup_by_gid()/all_groups() database readers, user_in_group()/user_groups()/current_process_groups()/process_in_group() membership checkers, require_group()/require_root() privilege guards, and GroupAudit with audit_groups()/find_groups_for_user_pattern()/groups_by_gid_range(). Start with the free tier to try group database patterns and grp pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free