Mercurial > piecrust2
view piecrust/publishing/base.py @ 1188:a7c43131d871
bake: Fix file write flushing problem with Python 3.8+
Writing the cache files fails in Python 3.8 because it looks like flushing
behaviour has changed. We need to explicitly flush. And even then, in very
rare occurrences, it looks like it can still run into racing conditions,
so we do a very hacky and ugly "retry" loop when fetching cached data :(
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 15 Jun 2021 22:36:23 -0700 |
parents | d709429f02eb |
children |
line wrap: on
line source
import os.path import time import logging from piecrust.chefutil import format_timed logger = logging.getLogger(__name__) FILE_MODIFIED = 1 FILE_DELETED = 2 class PublisherConfigurationError(Exception): pass class PublishingContext: def __init__(self): self.bake_out_dir = None self.bake_records = None self.processing_record = None self.was_baked = False self.preview = False self.args = None class Publisher: PUBLISHER_NAME = 'undefined' PUBLISHER_SCHEME = None def __init__(self, app, target, config): self.app = app self.target = target self.config = config self.log_file_path = None def setupPublishParser(self, parser, app): return def parseUrlTarget(self, url): raise NotImplementedError() def run(self, ctx): raise NotImplementedError() def getBakedFiles(self, ctx): for rec in ctx.bake_records.records: for e in rec.getEntries(): paths = e.getAllOutputPaths() if paths is not None: yield from paths def getDeletedFiles(self, ctx): for rec in ctx.bake_records.records: yield from rec.deleted_out_paths class InvalidPublishTargetError(Exception): pass class PublishingError(Exception): pass class PublishingManager: def __init__(self, appfactory, app): self.appfactory = appfactory self.app = app def run(self, target, force=False, preview=False, extra_args=None, log_file=None, log_debug_info=False, append_log_file=False): start_time = time.perf_counter() # Get publisher for this target. pub = self.app.getPublisher(target) if pub is None: raise InvalidPublishTargetError( "No such publish target: %s" % target) # Will we need to bake first? bake_first = pub.config.get('bake', True) # Setup logging stuff. hdlr = None root_logger = logging.getLogger() if log_file and not preview: logger.debug("Adding file handler for: %s" % log_file) mode = 'w' if append_log_file: mode = 'a' hdlr = logging.FileHandler(log_file, mode=mode, encoding='utf8') root_logger.addHandler(hdlr) if log_debug_info: _log_debug_info(target, force, preview, extra_args) if not preview: logger.info("Deploying to %s" % target) else: logger.info("Previewing deployment to %s" % target) # Bake first is necessary. records = None was_baked = False bake_out_dir = os.path.join(self.app.root_dir, '_pub', target) if bake_first: if not preview: bake_start_time = time.perf_counter() logger.debug("Baking first to: %s" % bake_out_dir) from piecrust.baking.baker import Baker baker = Baker( self.appfactory, self.app, bake_out_dir, force=force) records = baker.bake() was_baked = True if not records.success: raise Exception( "Error during baking, aborting publishing.") logger.info(format_timed(bake_start_time, "Baked website.")) else: logger.info("Would bake to: %s" % bake_out_dir) # Publish! logger.debug( "Running publish target '%s' with publisher: %s" % (target, pub.PUBLISHER_NAME)) pub_start_time = time.perf_counter() success = False ctx = PublishingContext() ctx.bake_out_dir = bake_out_dir ctx.bake_records = records ctx.was_baked = was_baked ctx.preview = preview ctx.args = extra_args try: success = pub.run(ctx) except Exception as ex: raise PublishingError( "Error publishing to target: %s" % target) from ex finally: if hdlr: root_logger.removeHandler(hdlr) hdlr.close() logger.info(format_timed( pub_start_time, "Ran publisher %s" % pub.PUBLISHER_NAME)) if success: logger.info(format_timed(start_time, 'Deployed to %s' % target)) return 0 else: logger.error(format_timed(start_time, 'Failed to deploy to %s' % target)) return 1 def find_publisher_class(app, name, is_scheme=False): attr_name = 'PUBLISHER_SCHEME' if is_scheme else 'PUBLISHER_NAME' for pub_cls in app.plugin_loader.getPublishers(): pub_sch = getattr(pub_cls, attr_name, None) if pub_sch == name: return pub_cls return None def find_publisher_name(app, scheme): pub_cls = find_publisher_class(app, scheme, True) if pub_cls: return pub_cls.PUBLISHER_NAME return None def _log_debug_info(target, force, preview, extra_args): import os import sys logger.info("---- DEBUG INFO START ----") logger.info("System:") logger.info(" sys.argv=%s" % sys.argv) logger.info(" sys.base_exec_prefix=%s" % sys.base_exec_prefix) logger.info(" sys.base_prefix=%s" % sys.base_prefix) logger.info(" sys.exec_prefix=%s" % sys.exec_prefix) logger.info(" sys.executable=%s" % sys.executable) logger.info(" sys.path=%s" % sys.path) logger.info(" sys.platform=%s" % sys.platform) logger.info(" sys.prefix=%s" % sys.prefix) logger.info("Environment:") logger.info(" cwd=%s" % os.getcwd()) logger.info(" pid=%s" % os.getpid()) logger.info("Variables:") for k, v in os.environ.items(): logger.info(" %s=%s" % (k, v)) logger.info("---- DEBUG INFO END ----")