Skip to content

Commit

Permalink
Add Python 3.12 compatibility for path fieldtype (#91)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: pyrco <105293448+pyrco@users.noreply.github.com>
Co-authored-by: Schamper <1254028+Schamper@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 4, 2024
1 parent a9808ec commit 58d5915
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 23 deletions.
42 changes: 25 additions & 17 deletions flow/record/fieldtypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
UTC = timezone.utc

PY_311 = sys.version_info >= (3, 11, 0)
PY_312 = sys.version_info >= (3, 12, 0)

PATH_POSIX = 0
PATH_WINDOWS = 1
Expand Down Expand Up @@ -645,28 +646,31 @@ def __new__(cls, *args):
for path_part in args:
if isinstance(path_part, pathlib.PureWindowsPath):
cls = windows_path
# The (string) representation of a pathlib.PureWindowsPath is not round trip equivalent if a
# path starts with a \ or / followed by a drive letter, e.g.: \C:\...
# Meaning:
#
# str(PureWindowsPath(r"\C:\WINDOWS/Temp")) !=
# str(PureWindowsPath(PureWindowsPath(r"\C:\WINDOWS/Temp"))),
#
# repr(PureWindowsPath(r"\C:\WINDOWS/Temp")) !=
# repr(PureWindowsPath(PureWindowsPath(r"\C:\WINDOWS/Temp"))),
#
# This would be the case though when using PurePosixPath instead.
#
# This construction works around that by converting all path parts
# to strings first.
args = (str(arg) for arg in args)
if not PY_312:
# For Python < 3.12, the (string) representation of a
# pathlib.PureWindowsPath is not round trip equivalent if a path
# starts with a \ or / followed by a drive letter, e.g.: \C:\...
# Meaning:
#
# str(PureWindowsPath(r"\C:\WINDOWS/Temp")) !=
# str(PureWindowsPath(PureWindowsPath(r"\C:\WINDOWS/Temp"))),
#
# repr(PureWindowsPath(r"\C:\WINDOWS/Temp")) !=
# repr(PureWindowsPath(PureWindowsPath(r"\C:\WINDOWS/Temp"))),
#
# This would be the case though when using PurePosixPath instead.
#
# This construction works around that by converting all path parts
# to strings first.
args = (str(arg) for arg in args)
elif isinstance(path_part, pathlib.PurePosixPath):
cls = posix_path
elif _is_windowslike_path(path_part):
# This handles any custom PurePath based implementations that have a windows
# like path separator (\).
cls = windows_path
args = (str(arg) for arg in args)
if not PY_312:
args = (str(arg) for arg in args)
elif _is_posixlike_path(path_part):
# This handles any custom PurePath based implementations that don't have a
# windows like path separator (\).
Expand All @@ -675,7 +679,11 @@ def __new__(cls, *args):
continue
break

return cls._from_parts(args)
if PY_312:
obj = super().__new__(cls)
else:
obj = cls._from_parts(args)
return obj

def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
Expand Down
32 changes: 26 additions & 6 deletions tests/test_fieldtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import hashlib
import os
import pathlib
import posixpath
import types
from datetime import datetime, timedelta, timezone

import pytest
Expand All @@ -12,6 +14,7 @@
from flow.record.fieldtypes import (
PATH_POSIX,
PATH_WINDOWS,
PY_312,
_is_posixlike_path,
_is_windowslike_path,
)
Expand Down Expand Up @@ -527,12 +530,29 @@ def test_digest():


def custom_pure_path(sep, altsep):
class CustomFlavour(pathlib._PosixFlavour):
def __new__(cls):
instance = pathlib._PosixFlavour.__new__(cls)
instance.sep = sep
instance.altsep = altsep
return instance
# Starting from Python 3.12, pathlib._Flavours are removed as you can
# now properly subclass pathlib.Path
# The flavour property of Path's is replaced by a link to e.g.
# posixpath or ntpath.
# See also: https://github.com/python/cpython/issues/88302
if PY_312:

class CustomFlavour:
def __new__(cls, *args, **kwargs):
flavour = types.ModuleType("mockpath")
flavour.__dict__.update(posixpath.__dict__)
flavour.sep = sep
flavour.altsep = altsep
return flavour

else:

class CustomFlavour(pathlib._PosixFlavour):
def __new__(cls):
instance = super().__new__(cls)
instance.sep = sep
instance.altsep = altsep
return instance

class PureCustomPath(pathlib.PurePath):
_flavour = CustomFlavour()
Expand Down

0 comments on commit 58d5915

Please sign in to comment.