# Expose file size, mode, etc from \`pathlib.Path.info\`

**URL:** <https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828>\
**Category:** Ideas\
**Created:** [September 19, 2025, 4:25am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828 "2025-09-19T04:25:16Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 19, 2025, 4:25am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/1 "2025-09-19T04:25:16Z")

</div>

To date we’ve not been able to add seemingly-useful methods like `pathlib.Path.size()` and `mode()`, because they would wastefully throw away most of the `os.stat_result`. That was just the nature of the `Path` interface - none of its methods cache their results. Instead, I’ve encouraged users to call `Path.stat()` and pull out [`os.stat_result`](https://docs.python.org/3/library/os.html#os.stat_result) fields.

But in Python 3.14 we have a new [`Path.info`](https://docs.python.org/3.14/library/pathlib.html#pathlib.Path.info) attribute ([discussion](https://discuss.python.org/t/ergonomics-of-new-pathlib-path-scandir/71721)), which is an annex for `Path` objects that caches filesystem information. For example, calling `p.info.is_dir()` twice will incur at most one `stat()`. Under the bonnet, `info` is mostly a wrapper around a stat result, except for paths generated by scanning directories where we wrap an [`os.DirEntry`](https://docs.python.org/3/library/os.html#os.DirEntry) instead.

`Path.info` has the following methods: `exists()`, `is_file()`, `is_dir()`, `is_symlink()`. These return booleans and suppress all `OSError` exceptions.

My suggestion is to add the following methods:

- `Path.info.size()` - content size in bytes (`int`)
- `Path.info.mode()` - 12-bit POSIX file mode (`int`)
- `Path.info.access_time()` - time of last read (`datetime`)
- `Path.info.modify_time()` - time of last write (`datetime`)
- `Path.info.create_time()` - time of birth (`datetime`)

These would accept _follow\_symlinks_ as an optional keyword-only argument and cache their results.

A few things I’m not sure about:

- Should we suppress exceptions like the existing methods? If not, do we cache and re-raise exceptions, e.g. what happens if a user calls `exists()` and then `size()` on a non-existing file?
- Is it alright for `mode()` to return an `int`, or would we prefer `enum.IntFlag` / similar?
- Is “mode” a good name, or would we prefer “posix\_permission” / similar? (I picked it mainly for consistency with the `chmod()` and `mkdir()` arguments)

Any other questions/criticism/ideas welcome of course.

Thanks all!

---

<div class="post-metadata">

**Author:** ![Dutcho](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dutcho/32/7074_2.png) [@Dutcho](https://discuss.python.org/u/Dutcho)\
**Post date:** [September 19, 2025, 8:41am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/2 "2025-09-19T08:41:19Z")

</div>

This would be most welcome, Barney.

For your bulleted questions:

- I think exceptions _must_ be re-raised, in a similar vein as for `next()` on an exhausted iterator
- `IntFlag` would definitely be my preference
- `mode()` seems brief and clear to me

For the methods, any reason to exclude `birth_time` and (Windows) `file_attributes`?

---

<div class="post-metadata">

**Author:** ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)\
**Post date:** [September 19, 2025, 9:30am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/3 "2025-09-19T09:30:32Z")

</div>

Can we add `Path.info.stat()` as well? It could be useful to get a raw timestamp, or some OS-specific info.

> [@barneygale](#):
>
> Should we suppress exceptions like the existing methods? If not, do we cache and re-raise exceptions, e.g. what happens if a user calls `exists()` and then `size()` on a non-existing file?

Maybe re-raise without caching, i.e. the next attempt will call `stat()` again? That seem easy to explain and implement.

> [@barneygale](#):
>
> Is it alright for `mode()` to return an `int`, or would we prefer `enum.IntFlag` / similar?

Yeah, IMO it’s fine for `mode`, and…

> [@barneygale](#):
>
> Is “mode” a good name, or would we prefer “posix\_permission” / similar? (I picked it mainly for consistency with the `chmod()` and `mkdir()` arguments)

that might be a good name for a future addition that returns some rich object, if we need an object that isn’t compatiple with `int`.

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 19, 2025, 6:49pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/4 "2025-09-19T18:49:07Z")

</div>

Thanks both!

I’ll experiment with using `enum.IntFlag` for `mode()`.

> [@Dutcho](#):
>
> For the methods, any reason to exclude `birth_time` and (Windows) `file_attributes`?

Good point on `birth_time()` - I’ll add that to the interface.

It’s difficult to know where to draw the line, but I reckon `file_attributes()` is perhaps too OS-specific and low-level for most pathlib users. I’ve excluded a wrapper of `st_flags` for the same reason.

> [@encukou](#):
>
> Can we add `Path.info.stat()` as well? It could be useful to get a raw timestamp, or some OS-specific info.

I’m a little reluctant because we already have `Path.stat()`, and that method doesn’t throw away information like `Path.exists()` does.

Perhaps we could look at adding it later, if there’s demand?

I appreciate the suggestions nonetheless 🙂

---

<div class="post-metadata">

**Author:** ![cmaloney](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cmaloney/32/15851_2.png) [@cmaloney](https://discuss.python.org/u/cmaloney)\
**Post date:** [September 19, 2025, 7:04pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/5 "2025-09-19T19:04:21Z")

</div>

Connecting a similar parallel thread around adding `os.statx` (linux system call): [gh-83714: Use statx on Linux 4.11 and later in os.stat by jbosboom · Pull Request #136334 · python/cpython · GitHub](https://github.com/python/cpython/pull/136334#issuecomment-3307397806)

Would be nice to expose the newer `statx` data and/or some wrapper which makes `stat`, `statx`, etc. all look/feel more normalized. Not sure how likely it is that more kernels will expose something statx-like though (can request subsets of information and some choices around how cached the data can be)

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [September 19, 2025, 7:33pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/6 "2025-09-19T19:33:31Z")

</div>

> [@barneygale](#):
>
> It’s difficult to know where to draw the line, but I reckon `file_attributes()` is perhaps too OS-specific and low-level for most pathlib users.

Maybe, but I could say the same about `mode` 🙂 Generally I find that the Unix-focused file mode is too simplistic to use on Windows, but it’s an “attractive nuisance” for people who are looking for a quick answer.

I’m not seriously suggesting we omit `mode`, of course, but I do think your argument against `file_attributes` is weak. (A stronger argument, that I can’t really dispute, is that the `file_attributes` fields in the `stat` object are rarely used.)

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 20, 2025, 5:47pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/7 "2025-09-20T17:47:49Z")

</div>

Playing around with using `enum.IntFlag` for the `mode()` return value, and the result is pretty nice I think:

```python
>>> from pathlib import Path
>>> path = Path('README.rst')
>>> path.info.mode()
<Mode.USER_READ|USER_WRITE|GROUP_READ|GROUP_WRITE|OTHER_READ: 436>
>>> from pathlib.types import Mode
>>> path.chmod(path.info.mode() | Mode.USER_EXECUTE)
>>> path = Path(path) # discard info cache
>>> path.info.mode()
<Mode.USER_READ|USER_WRITE|USER_EXECUTE|GROUP_READ|GROUP_WRITE|OTHER_READ: 500>

```

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [September 20, 2025, 9:28pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/8 "2025-09-20T21:28:03Z")

</div>

Love the idea.

But, about the mode, isn’t this bit-array just a detail of the underlying representation? Why not just expose all of the individual flags as Boolean methods? I think that will make the client code that uses this information much more legible (which is very important) at the cost of making pathlib slightly longer (which is much less important).

---

<div class="post-metadata">

**Author:** ![jbosboom](https://avatars.discourse-cdn.com/v4/letter/j/b5e925/32.png) [@jbosboom](https://discuss.python.org/u/jbosboom)\
**Post date:** [September 21, 2025, 6:10am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/9 "2025-09-21T06:10:08Z")

</div>

Are the timestamps lossy seconds-floats or exact nanoseconds-ints? (pathlib doesn’t have any means to set times except for `Path.touch()`, so maybe roundtripping is out-of-scope. The recent multigrain timestamp changes in Linux 6.13 allow timestamps with granularity better than a jiffy; not sure if it’s good enough for float imprecision to fool make-like scripts.)

Do you plan to include other types of files (FIFOs, block and character devices, Unix sockets)? At least according to [MSDN GetFileType documentation](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype), Windows has “disk”, character device and pipe files.

Regarding generalizing statx: Windows kinda supports querying a subset of information, but AIUI it’s limited to choosing between different structs; there’s no obvious way (besides failing the call) to indicate that some information is not available. I also couldn’t find anything analogous to statx’s force-sync/don’t-sync flag, which I find surprising given Windows’s long-term focus on network filesystems in large corporate intranets. Unless someone else finds something, I don’t see anything to do here.

Looking at the currently defined Windows attribute bits, macOS/BSD flags, and statx attribute bits, there are some commonalities:

| Windows | Mac | Linux | meaning |
| --- | --- | --- | --- |
| FILE\_ATTRIBUTE\_HIDDEN | UF\_HIDDEN | filename starts with ‘.’ | only shown when user asks for them |
| FILE\_ATTRIBUTE\_COMPRESSED | UF\_COMPRESSED | STATX\_ATTR\_COMPRESSED | storage size \< nominal size |
| FILE\_ATTRIBUTE\_ENCRYPTED | ? | STATX\_ATTR\_ENCRYPTED | may require special permissions to read or modify |
| FILE\_ATTRIBUTE\_READONLY (sort of) | UF\_IMMUTABLE | STATX\_ATTR\_IMMUTABLE | requires special permissions to modify |
| FILE\_ATTRIBUTE\_ARCHIVE (inverted, sort of) | UF\_NODUMP | STATX\_ATTR\_NODUMP | exclude from backups that bother to check this flag |
| IO\_REPARSE\_TAG\_MOUNT\_POINT | [statfs f\_mntonname != f\_mntfromname](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/statfs.2.html) | STATX\_ATTR\_MOUNT\_ROOT | what it says on the tin |

Hiddenness seems user-relevant enough to expose in a cross-platform way. Mount-root is useful on Linux because the traditional `st_dev` test does not detect bind mounts on the same filesystem and `openat2` with `RESOLVE_NO_XDEV` may trigger automounts and auditing\[1\] – but I’m not sure that generalizes in a way useful to implement, e.g., `rm --one-file-system` on Windows or macOS too. (Also, mount points and mount roots are technically not the same thing.)

(For the rest: the storage size of a file can differ from the nominal size also if the file has holes\[2\]; getting encryption keys or clearing the immutable bit requires OS-level interaction anyway; approximately zero programs check the nodump flag.)

* * *

1. and isn’t (yet?) available to Python 

2. and when considering the total storage of a set of files, also due to extent sharing

---

<div class="post-metadata">

**Author:** ![Melendowski](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/melendowski/32/2960_2.png) [@Melendowski](https://discuss.python.org/u/Melendowski)\
**Post date:** [September 21, 2025, 11:50am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/10 "2025-09-21T11:50:45Z")

</div>

I’m torn.

Explaining how permission bits in unix, at work to new hires, is always cumbersome. The use of `IntFlag` is really slick and explains the concept nicely imo. However the explicitness of having 9 distinct properties for getting and setting each bit removes the burden to learn the bits completely.

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 21, 2025, 1:28pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/11 "2025-09-21T13:28:07Z")

</div>

It’s an interesting point. I suppose the main advantage of a bit array is compatibility with `os.chmod()`, `os.mkdir()`, and other such functions that accept a mode. Also the ability to manipulate the value before passing it back to `chmod()` as in my previous example.

If we dropped the mode int, presumably we’d have methods like `path.info.executable()` instead. I think that could be a little misleading, as there are other factors that can prevent the execution of a file beside its mode bits.

---

<div class="post-metadata">

**Author:** ![Nineteendo](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nineteendo/32/19122_2.png) [@Nineteendo](https://discuss.python.org/u/Nineteendo)\
**Post date:** [September 21, 2025, 1:31pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/12 "2025-09-21T13:31:54Z")

</div>

Did you also consider something like this? (Can probably still be an int subclass)

```python
>>> path.info.mode()
Modes(user=<Mode.READ|WRITE|EXECUTE: 7>, group=<Mode.READ|WRITE: 6>, other=<Mode.READ: 4>)

```

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [September 21, 2025, 1:56pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/13 "2025-09-21T13:56:52Z")

</div>

> [@barneygale](#):
>
> If we dropped the mode int, presumably we’d have methods like `path.info.executable()` instead.

It seems to me that making the parts of the `mode` attribute into methods is a bad idea, in the sense that we should add methods based on _use cases_, not just wrapping some bit tests (which aren’t sufficient on their own to address the underlying use cases). If you wanted to add a `path.info.is_executable()` method, I’d be fine with that, as long as it did what it says, and checks whether a file is executable. As you noted, though, that’s _not_ the same as just checking a mode bit (not even on Unix, and much less so on Windows).

The use case for having a mode attribute is that “people want access to the POSIX mode” - which is a bit circular, but not worth worrying about given the huge historical precedent. But if we move away from providing just the mode and letting people do their own thing, I think we have to do it _properly_, and not just wrap a bit of bit checking in a method.

And personally, while I’d _like_ accurate `is_readable()`, `is_writeable()` and `is_executable()` methods, I don’t think it’s worth the significant amount of effort needed to provide correct cross-platform implementations of them.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [September 22, 2025, 7:21am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/14 "2025-09-22T07:21:08Z")

</div>

Just for clarity, I suggest a couple dataclasses for the mode with some useful factories for the most common cases (saves users from having to look things up):

```python
@dataclass
class BasicPermission:
    read: bool
    write: bool
    execute: bool

@dataclass
class FilePermissions:
    owner: BasicPermission
    group: BasicPermission
    others: BasicPermission

    def as_int(self) -> int:
        return ...

    @classmethod
    def from_int(cls, mode: int, /) -> FilePermissions:
        return FilePermissions(...)

    @classmethod
    def public_file(cls) -> FilePermissions:
        """
        644 → rw-r--r--
        Owner can read/write, group and others can read. Common for text 
        files, configs, documents.
        """
        return FilePermissions.from_int(0o644)

    @classmethod
    def private_file(cls) -> FilePermissions:
        """
        600 → rw-------
        Only owner can read/write. Used for private files (SSH keys,
        credentials).
        """
        return FilePermissions.from_int(0o600)

    @classmethod
    def shared_file(cls) -> FilePermissions:
        """
        664 → rw-rw-r--
        Owner and group can read/write, others can only read. Used in
        collaborative environments.
        """
        return FilePermissions.from_int(0o664)

    @classmethod
    def executable_script(cls) -> FilePermissions:
        """
        755 → rwxr-xr-x
        Owner can read/write/execute, others can read/execute. For executable
        scripts.
        """
        return FilePermissions.from_int(0o755)

    @classmethod
    def system_directory(cls) -> FilePermissions:
        """
        755 → rwxr-xr-x
        Owner can read/write/enter, others can read/enter but not write. Very
        common for system dirs like /usr, /bin.
        """
        return FilePermissions.from_int(0o755)

    @classmethod
    def private_directory(cls) -> FilePermissions:
        """
        700 → rwx------
        Only owner can access. Used for private directories like ~/.ssh.
        """
        return FilePermissions.from_int(0o700)

    @classmethod
    def shared_directory(cls) -> FilePermissions:
        """
        775 → rwxrwxr-x
        Owner and group have full access, others can read/enter. Used in
        shared group dirs.
        """
        return FilePermissions.from_int(0o775)

    @classmethod
    def temporary_directory(cls) -> FilePermissions:
        """
        777 → rwxrwxrwx
        Everyone has full access. Rare and generally unsafe, but sometimes
        used for temporary dirs like /tmp.
        """
        return FilePermissions.from_int(0o777)

```

Then, we could allow users to use the `FilePermissions` in place of integers:

```python
Path.chmod(self, mode: int | FilePermissions, ...) -> None: ...
Path.mkdir(self, mode: int | FilePermissions, ...) -> None: ...

```

This allows users to sidestep the (what I feel are) anachronistic octal values and code in an arguably more Pythonic, straightforward interface. E.g.,

```python
if path.info.permissions().user.executable: ...
path.chmod(FilePermissions.executable_script())
path.mkdir(FilePermissions.private_directory())

```

---

<div class="post-metadata">

**Author:** ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)\
**Post date:** [September 22, 2025, 8:02am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/15 "2025-09-22T08:02:04Z")

</div>

> [@barneygale](#):
>
> `Mode.USER_READ`

This already has a standard name: [`stat.S_IRUSR`](https://docs.python.org/3/library/stat.html#stat.S_IRUSR)

IMO, it’d be best to expose all of `stat()`, and/or give the mode as `int`. Those have existing infrastructure around them (even if it is clunky and decades old), and easy to wrap if someone wants something better.

> [@barneygale](#):
>
> I’m a little reluctant [to add `stat()`] because we already have `Path.stat()`, and that method doesn’t throw away information like `Path.exists()` does.

That method doesn’t cache, though. If 3 different libraries build 3 different easy-to-use wrappers around `Path.info.stat()`, they _all_ get cached info. With only `Path.stat()`, caching becomes complicated even for a single library.  
That’s why I think this should expose the “source” data, even if it the format is friendly for the (Unix) kernel more than users. Adding more complicated representations would be a bonus if we get them right, and useless if we don’t.

---

<div class="post-metadata">

**Author:** ![EpicWink](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/epicwink/32/17968_2.png) [@EpicWink](https://discuss.python.org/u/EpicWink)\
**Post date:** [September 22, 2025, 11:53pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/16 "2025-09-22T23:53:33Z")

</div>

I would prefer a more structured object over the stat interface and permissions int, as that would be easier to adhere to in implementations of Path on non-posix “filesystems”. If you intend only to add `info` to PosixPath, then I guess this point is moot

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 23, 2025, 8:20pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/17 "2025-09-23T20:20:11Z")

</div>

If you’re thinking of _virtual_ filesystems here (archives, cloud storage, etc) then I think an `int` is an OK choice. `.tar`, `.zip`\[1\] and `.iso` files store POSIX permissions as-is without generalising into another format. IIRC Git only supports setting the executable bit, but still uses octal permissions in e.g. `git fast-import`, perhaps elsewhere. For those formats at least, implementing a `PathInfo.mode()` method is simplest if it returns an `int`.

* * *

1. shifted left by 16 bits

---

<div class="post-metadata">

**Author:** ![Monarch](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/monarch/32/18530_2.png) [@Monarch](https://discuss.python.org/u/Monarch)\
**Post date:** [September 23, 2025, 8:31pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/18 "2025-09-23T20:31:46Z")

</div>

For some prior art, Rust’s `Path` offers a similar concept via [`Path::metadata`](https://doc.rust-lang.org/std/path/struct.Path.html#method.metadata). Notably, Rust’s `Path` type has no direct `stat()` equivalent precisely because of the platform-specific nature of it. Instead, its [`Metadata`](https://doc.rust-lang.org/std/fs/struct.Metadata.html) API only exposes the cross-platform bits:

1. `is_dir() → bool`
2. `is_file() → bool`
3. `is_symlink() → bool`
4. `len() → int` \[1\] – file size
5. `modified() → datetime` \[1:1\]\[2\] – Unix `mtime` or Windows `ftLastWriteTime`
6. `accessed() → datetime` \[1:2\]\[2:1\] – Unix `atime` or Windows `ftLastAccessTime`
7. `created() → datetime` \[1:3\]\[2:2\] – `statx.btime` (kernel ≥ 4.11)/`birthtime` or Windows `ftCreationTime`
8. `permissions() → Permissions` – a struct with a single cross-platform bit of information, `readonly() → bool`. On Windows, this checks `FILE_ATTRIBUTE_READONLY`, and on Unix, it returns true if none of the owner/group/other write bits are set.

Because `pathlib.Path` already provides `stat()`, I agree with @encukou that exposing a cached `stat()` via `Path.info` makes sense. If `Path` lacked `stat()`, I’d be against adding it to `PathInfo`.\[3\]

However, I’m not convinced about a dedicated `PathInfo.mode()`. I’d rather see a higher-level, cross-platform object akin to Rust’s `Permissions`.

One possible design, which aligns with the fact that `Path.info` already returns [different types at runtime (`_WindowsPathInfo` on Windows, `_PosixPathInfo` on POSIX)](https://github.com/python/cpython/blob/460ad1aa1bae2fb06284611e786a475787ce9975/Lib/pathlib/_os.py#L484), would be:

- `Path.info.permissions` always returns a cross-platform `Permissions` object with only universally supported bits (like `readonly() -> bool`).
- `_PosixPathInfo.permissions` returns a `PosixPermissions` object with full POSIX mode information.
- `_WindowsPathInfo.permissions` returns a `WindowsPermissions` object with Windows-specific flags.

Users can then `match`/`isinstance` to get the platform-specific API. This makes it so that platform-specific API is an explicit opt-in and avoids polluting a cross-platform object with platform-specific methods.\[4\]

Alternatively, if you think the number of `$Type`, `Windows$Type`, and `Posix$Type` is getting ridiculous, we can just put methods directly on `.info` instead of `.info.permissions`.

* * *

1. I’ve deliberately written these signatures using the closest Python types to make them more comprehensible. 

2. In Rust, these time methods can fail and return an error; I’ve left that detail out to keep the comparison straightforward. 

3. Although, personally, I would prefer to see `Path.stat()` deprecated from `Path` and available only on `PosixPath`, I am aware this might be unrealistic at this point, so we have to live with it. 

4. Rust itself uses a similar pattern for platform-specific metadata: `std::fs::Metadata` provides the cross-platform bits, while `std::os::unix::fs::MetadataExt`, `std::os::windows::fs::MetadataExt`, and `std::os::unix::fs::PermissionsExt` add OS-specific bits through its trait system rather than separate classes.

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 23, 2025, 9:38pm UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/19 "2025-09-23T21:38:12Z")

</div>

Very nice, thanks @Monarch. Quite tempting to lift the rust API almost as-is!

I like that `readonly()` gets its own method, as the one piece of cross-platform information. The Unix-specific `PermissionsExt` has `mode()`, `set_mode()` and `from_mode()` methods but nothing more specific, which tbh I also like.

Rust’s `chmod()`-equivalent [`set_permissions()`](https://doc.rust-lang.org/std/fs/fn.set_permissions.html) accepts a `Permissions` object. We’re at something of a disadvantage in that our `os.chmod()` and `os.mkdir()` functions (which are used by pathlib) accept integer permissions, even on Windows. So perhaps our `WindowsPathPermissions` class needs a `mode()` function, but it’s faked entirely from the `readonly()` data, very roughly:

```python
class WindowsPathPermissions:
    def __init__ (self, readonly):
        self._readonly = readonly

    def readonly():
        return self._readonly

    def mode():
        return 0o444 if self._readonly else 0o666

class PosixPathPermissions:
    def __init__ (self, mode):
        def._mode = mode

    def readonly():
        return (self._mode & 0o222) == 0

    def mode():
        return self._mode

class Path:
    def chmod(self, mode):
        if isinstance(mode, PathPermissions):
            mode = mode.mode()
        os.chmod(self, mode)

```

edit: alternatively we put it in a utility function that’s called from `Path.chmod()` and `Path.mkdir()`:

```python
def _get_posix_mode(obj):
    if isinstance(obj, PosixPathPermissions):
        return obj.mode()
    if isinstance(obj, PathPermissions):
        return 0o444 if obj.readonly() else 0o666
    if isinstance(obj, int):
        return obj
    raise TypeError(...)

```

---

<div class="post-metadata">

**Author:** ![barneygale](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barneygale/32/1882_2.png) [@barneygale](https://discuss.python.org/u/barneygale)\
**Post date:** [September 24, 2025, 12:05am UTC](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828/20 "2025-09-24T00:05:45Z")

</div>

On `stat()`:

[`Path.info`](https://docs.python.org/3.14/library/pathlib.html#pathlib.Path.info) is currently documented as a [`pathlib.types.PathInfo`](https://docs.python.org/3.14/library/pathlib.html#pathlib.types.PathInfo) object. That protocol will eventually be helpful for people building `Path`-like classes for virtual filesystems, and asking those folks to create an `os.stat_result`-ish object is annoying/borderline nonsensical.

So we’ll need to change the documentation to describe the actual return type\[1\], probably exposed as `pathlib.PathInfo` (though that name conflicts with the protocol’s). I guess I’ll move the fledgling `pathlib.types.PathInfo` protocol documentation to a new page to avoid similar information appearing twice. All this is fine I think. I’ll try to get a PR together.

* * *

1. more likely, a non-protocol base type that can’t be instantiated by users

[Next page](https://discuss.python.org/t/expose-file-size-mode-etc-from-pathlib-path-info/103828.md?page=2)
