Our code makes extensive use of the fcntl
-module to lock files -- to prevent multiple instances of the program from stepping over each other. This works fine on Linux, but, as we just discovered, there is no fcntl on Windows...
Fortunately, we don't need the multiple instance-safety on Windows and could just fake it there. But replacing the existing use of fcntl
-module, its constants (like fcntl.LOCK_EX
) and function (fcntl.flock
) is something, I want to avoid.
So I tried creating an fcntl.py
of my own with something like:
import sys
import os
if os.name == 'posix':
syspath = sys.path
sys.path = sys.path[1:] # Remove current directory from search path
import fcntl as realfcntl
LOCK_EX = realfcntl.LOCK_EX
LOCK_SH = realfcntl.LOCK_SH
LOCK_UN = realfcntl.LOCK_UN
sys.path = syspath
def flock(fd, mode):
return realfcntl.flock(fd, mode)
else:
# Fake it:
LOCK_EX = -1
LOCK_SH = -1
LOCK_UN = -1
def flock(fd, mode):
print('Pretending to manipulate locking on FD %s' % fd, file = sys.stderr)
To my dismay, this fails at the import time on Unix, on line 8: module 'fcntl' has no attribute 'LOCK_EX'
, which tells me, my attempt to trick it into loading the real fcntl failed.
How can the wrapper like mine load the real module (being wrapped)?
I believe the easiest way to do this is to simply force a module called fcntl
to exist in the entry point of your code:
YOUR_REPOSITORY/
YOUR_PACKAGE/
__init__.py
_activate_fcntl.py
# YOUR_PACKAGE/__init__.py
# This import must be before all other import statements which reference `YOUR_PACKAGE`
from . import _activate_fcntl
# Other imports
...
# YOUR_PACKAGE/_activate_fcntl.py
from __future__ import annotations
import sys
try:
# Don't need to mess around with checking `os.name` here, if it exists, then it
# exists.
import fcntl
except ModuleNotFoundError:
from inspect import getdoc
from types import ModuleType
class fake_fcntl_module_class(ModuleType):
"""
This will be your fake `fcntl` module's docstring, if you need it.
"""
def __init__(self) -> None:
"""Class constructor - initialise your module with a name and docstring"""
super().__init__(name="fcntl", doc=getdoc(self))
# =============================================================================
# Treat the remaining contents of this class as the body of your `fcntl` module
# =============================================================================
LOCK_EX = -1
LOCK_SH = -1
LOCK_UN = -1
# Typing definitions taken from
# https://github.com/python/typeshed/blob/main/stdlib/fcntl.pyi
# `flock` is a module-level function, so imitate this using a `@staticmethod`
@staticmethod
def flock(__fd: int, __mode: int) -> None:
print("Pretending to manipulate locking on FD %s" % __fd, file=sys.stderr)
# Create the `fcntl` module object
fcntl = fake_fcntl_module_class()
# Forcefully replace whatever `fcntl` is available
sys.modules["fcntl"] = fcntl