utils.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # coding: utf-8
  2. # nm.debian.org website backend
  3. #
  4. # Copyright (C) 2012 Enrico Zini <enrico@debian.org>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as
  8. # published by the Free Software Foundation, either version 3 of the
  9. # License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. import tempfile
  19. import os.path
  20. import os
  21. import errno
  22. import shutil
  23. from io import StringIO
  24. class atomic_writer(object):
  25. """
  26. Atomically write to a file
  27. """
  28. def __init__(self, fname, mode=0o664, sync=True):
  29. self.fname = fname
  30. self.mode = mode
  31. self.sync = sync
  32. dirname = os.path.dirname(self.fname)
  33. if not os.path.isdir(dirname):
  34. os.makedirs(dirname)
  35. self.outfd = tempfile.NamedTemporaryFile(dir=dirname)
  36. def __enter__(self):
  37. return self.outfd
  38. def __exit__(self, exc_type, exc_val, exc_tb):
  39. if exc_type is None:
  40. self.outfd.flush()
  41. if self.sync:
  42. os.fdatasync(self.outfd.fileno())
  43. os.fchmod(self.outfd.fileno(), self.mode)
  44. os.rename(self.outfd.name, self.fname)
  45. self.outfd.delete = False
  46. self.outfd.close()
  47. return False
  48. def stream_output(proc):
  49. """
  50. Take a subprocess.Popen object and generate its output, as pairs of (tag,
  51. line) couples. Tag can be O for stdout, E for stderr and R for return
  52. value.
  53. Note that the output is not line-split.
  54. R is always the last bit that gets generated.
  55. """
  56. import os
  57. import fcntl
  58. import select
  59. fds = [proc.stdout, proc.stderr]
  60. tags = ["O", "E"]
  61. # Set both pipes as non-blocking
  62. for fd in fds:
  63. fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
  64. # Multiplex stdout and stderr with different tags
  65. while len(fds) > 0:
  66. s = select.select(fds, (), ())
  67. for fd in s[0]:
  68. idx = fds.index(fd)
  69. buf = fd.read()
  70. if buf:
  71. yield tags[idx], buf
  72. else:
  73. fds.pop(idx)
  74. tags.pop(idx)
  75. res = proc.wait()
  76. yield "R", res
  77. class StreamStdoutKeepStderr(object):
  78. """
  79. Stream lines of standard output from a Popen object, keeping all of its
  80. stderr inside a StringIO
  81. """
  82. def __init__(self, proc):
  83. self.proc = proc
  84. self.stderr = StringIO()
  85. def __iter__(self):
  86. last_line = None
  87. for tag, buf in stream_output(self.proc):
  88. if tag == "O":
  89. for l in buf.splitlines(True):
  90. if last_line is not None:
  91. l = last_line + l
  92. last_line = None
  93. if l.endswith("\n"):
  94. yield l
  95. else:
  96. last_line = l
  97. elif tag == "E":
  98. self.stderr.write(unicode(buf))
  99. if last_line is not None:
  100. yield last_line
  101. class NamedTemporaryDirectory(object):
  102. """
  103. Create a temporary directory, and delete it at the end
  104. """
  105. def __init__(self, parent=None):
  106. self.pathname = tempfile.mkdtemp(dir=parent)
  107. def __enter__(self):
  108. return self.pathname
  109. def __exit__(self, exc_type, exc_val, exc_tb):
  110. shutil.rmtree(self.pathname)
  111. return False
  112. def require_dir(pathname, mode=0o777):
  113. """
  114. Make sure pathname exists, creating it if not.
  115. """
  116. try:
  117. os.makedirs(pathname, mode)
  118. except OSError as e:
  119. if e.errno != errno.EEXIST:
  120. raise
  121. # Taken from werkzeug
  122. class cached_property(object):
  123. """A decorator that converts a function into a lazy property. The
  124. function wrapped is called the first time to retrieve the result
  125. and then that calculated result is used the next time you access
  126. the value::
  127. class Foo(object):
  128. @cached_property
  129. def foo(self):
  130. # calculate something important here
  131. return 42
  132. The class has to have a `__dict__` in order for this property to
  133. work.
  134. """
  135. # implementation detail: this property is implemented as non-data
  136. # descriptor. non-data descriptors are only invoked if there is
  137. # no entry with the same name in the instance's __dict__.
  138. # this allows us to completely get rid of the access function call
  139. # overhead. If one choses to invoke __get__ by hand the property
  140. # will still work as expected because the lookup logic is replicated
  141. # in __get__ for manual invocation.
  142. def __init__(self, func, name=None, doc=None):
  143. self.__name__ = name or func.__name__
  144. self.__module__ = func.__module__
  145. self.__doc__ = doc or func.__doc__
  146. self.func = func
  147. def __get__(self, obj, type=None):
  148. if obj is None:
  149. return self
  150. # For our needs, we can use None instead of werkzeug's _missing
  151. value = obj.__dict__.get(self.__name__, None)
  152. if value is None:
  153. value = self.func(obj)
  154. obj.__dict__[self.__name__] = value
  155. return value