我最近将我的项目升级到Django 2.0,并且错误开始出现。首先,每当我启动django runserver时,我都会使用django_gulp
来启动gulp进程。我正在使用runserver_plus
branch of the django_gulp
project。这是来自django_gulp
项目的the relevant code snippet,它会在subprocess.Popen
调用。这个调用在Django 1.11.x中正常运行。
from __future__ import print_function
import atexit
import os
import psutil
import subprocess
import sys
import traceback
from signal import SIGTERM
from concurrent.futures import ThreadPoolExecutor
from django.core.management.base import CommandError
from django.conf import settings
from django_extensions.management.commands.runserver_plus import Command \
as DjangoExtensionsRunserverCommand
from env_tools import load_env
class Command(DjangoExtensionsRunserverCommand):
"""
Subclass the RunserverCommand from Staticfiles to set up our gulp
environment.
"""
def __init__(self, *args, **kwargs):
self.cleanup_closing = False
self.gulp_process = None
super(Command, self).__init__(*args, **kwargs)
@staticmethod
def gulp_exited_cb(future):
if future.exception():
print(traceback.format_exc())
children = psutil.Process().children(recursive=True)
for child in children:
print('>>> Killing pid {}'.format(child.pid))
child.send_signal(SIGTERM)
print('>>> Exiting')
# It would be nice to be able to raise a CommandError or use
# sys.kill here but neither of those stop the runserver instance
# since we're in a thread. This method is used in django as well.
os._exit(1)
def handle(self, *args, **options):
try:
env = load_env()
except IOError:
env = {}
# XXX: In Django 1.8 this changes to:
# if 'PORT' in env and not options.get('addrport'):
# options['addrport'] = env['PORT']
if 'PORT' in env and not args:
args = (env['PORT'],)
# We're subclassing runserver, which spawns threads for its
# autoreloader with RUN_MAIN set to true, we have to check for
# this to avoid running gulp twice.
if not os.getenv('RUN_MAIN', False):
pool = ThreadPoolExecutor(max_workers=1)
gulp_thread = pool.submit(self.start_gulp)
gulp_thread.add_done_callback(self.gulp_exited_cb)
return super(Command, self).handle(*args, **options)
def kill_gulp_process(self):
if self.gulp_process.returncode is not None:
return
self.cleanup_closing = True
self.stdout.write('>>> Closing gulp process')
self.gulp_process.terminate()
def start_gulp(self):
self.stdout.write('>>> Starting gulp')
gulp_command = getattr(settings, 'GULP_DEVELOP_COMMAND', 'gulp')
self.gulp_process = subprocess.Popen(
[gulp_command],
shell=True,
stdin=subprocess.PIPE,
stdout=self.stdout,
stderr=self.stderr)
if self.gulp_process.poll() is not None:
raise CommandError('gulp failed to start')
self.stdout.write('>>> gulp process on pid {0}'
.format(self.gulp_process.pid))
atexit.register(self.kill_gulp_process)
self.gulp_process.wait()
if self.gulp_process.returncode != 0 and not self.cleanup_closing:
raise CommandError('gulp exited unexpectedly')
请注意self.stdout
和self.stderr
的参数是subprocess.Popen
的参数,是对django.core.management.base.OutputWrapper
的引用。到目前为止,我所能说的是Django 1.11的OutputWrapper
类继承自object
,而Django 2.0的OutputWrapper
类继承自TextIOBase
。
这是我得到的错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.6/subprocess.py", line 667, in __init__
errread, errwrite) = self._get_handles(stdin, stdout, stderr)
File "/usr/local/lib/python3.6/subprocess.py", line 1184, in _get_handles
c2pwrite = stdout.fileno()
io.UnsupportedOperation: fileno
如果这是django_gulp
问题,我会在该回购中创建一个问题和/或PR来修复此问题。但是,就目前而言,我希望在我自己的项目中实现这一点。
我还应该提一下,我是在一个docker-compose环境中运行它,所以也许是因为它导致了错误。我还没有在非Docker环境中测试过这个。
根据this answer,似乎子流程代码可能假设流具有文件描述符,在这种情况下没有文件描述符。
答案 0 :(得分:4)
您看到此错误的原因是文件类对象是Python抽象。操作系统和其他进程不了解这种抽象,他们只知道文件描述符。因此,必须将有效的文件描述符传递给Popen。
您可以从OutputWrapper
_out
包裹的信息流
self.gulp_process = subprocess.Popen(
#...
stdout=self.stdout._out,
stderr=self.stderr._out)
或者,您可以传递标准文件编号以获得标准输出和错误:
self.gulp_process = subprocess.Popen(
#...
stdout=1,
stderr=2)