Skip to content

Commit

Permalink
Merge pull request #7 from plone/refactor-utility
Browse files Browse the repository at this point in the history
Refactor exporter/importer utilities to site adapters
  • Loading branch information
davisagli authored Apr 17, 2024
2 parents 71dc69f + 1070049 commit 7c0258b
Show file tree
Hide file tree
Showing 19 changed files with 196 additions and 174 deletions.
27 changes: 6 additions & 21 deletions src/plone/exportimport/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from pathlib import Path
from plone import api
from plone.exportimport import interfaces
from plone.exportimport.exporters import get_exporter
from plone.exportimport.importers import get_importer
from plone.exportimport.utils import cli as cli_helpers
from Products.CMFPlone.Portal import PloneSite
from zope.component import getUtility
from zope.component.hooks import setSite

import argparse
import sys
Expand Down Expand Up @@ -38,13 +35,6 @@ def _parse_args(description: str, options: dict, args: list):
return namespace


def _export_site(site: PloneSite, export_path: Path):
setSite(site)
utility = getUtility(interfaces.IExporterUtility, "plone.exporter")
with api.env.adopt_roles(["Manager"]):
return utility.export_site(site, export_path)


def exporter_cli(args=sys.argv):
"""Export a Plone site."""
logger = cli_helpers.get_logger("Exporter")
Expand All @@ -56,19 +46,13 @@ def exporter_cli(args=sys.argv):
logger.error(f"{namespace.path} does not exist, please create it first.")
sys.exit(1)
site = cli_helpers.get_site(app, namespace.site, logger)
results = _export_site(site, path)
with api.env.adopt_roles(["Manager"]):
results = get_exporter(site).export_site(path)
logger.info(f" Using path {path} to export content from Plone site at /{site.id}")
for item in results[1:]:
logger.info(f" Wrote {item}")


def _import_site(site: PloneSite, export_path: Path):
setSite(site)
utility = getUtility(interfaces.IImporterUtility, "plone.importer")
with api.env.adopt_roles(["Manager"]):
return utility.import_site(site, export_path)


def importer_cli(args=sys.argv):
"""Import content to a Plone site."""
logger = cli_helpers.get_logger("Importer")
Expand All @@ -80,7 +64,8 @@ def importer_cli(args=sys.argv):
logger.error(f"{namespace.path} does not exist, aborting import.")
sys.exit(1)
site = cli_helpers.get_site(app, namespace.site, logger)
results = _import_site(site, path)
with api.env.adopt_roles(["Manager"]):
results = get_importer(site).import_site(path)
logger.info(f" Using path {path} to import content to Plone site at /{site.id}")
for item in results:
logger.info(f" - {item}")
4 changes: 2 additions & 2 deletions src/plone/exportimport/deserializers/blob.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path
from plone.dexterity.interfaces import IDexterityContent
from plone.exportimport import settings
from plone.exportimport.interfaces import IExportImportBlobsMarker
from plone.exportimport.interfaces import IExportImportRequestMarker
from plone.exportimport.utils import path as path_utils
from plone.namedfile.interfaces import INamedField
from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
Expand All @@ -24,7 +24,7 @@ def load_blob(path: str) -> bytes:
return codecs.decode(data, "base64")


@adapter(INamedField, IDexterityContent, IExportImportBlobsMarker)
@adapter(INamedField, IDexterityContent, IExportImportRequestMarker)
@implementer(IFieldDeserializer)
class ExportImportNamedFieldDeserializer(DefaultFieldDeserializer):
def __call__(self, value):
Expand Down
15 changes: 15 additions & 0 deletions src/plone/exportimport/deserializers/blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from plone.exportimport.interfaces import IExportImportRequestMarker
from plone.restapi.behaviors import IBlocks
from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
from plone.restapi.interfaces import IFieldDeserializer
from plone.schema import IJSONField
from zope.component import adapter
from zope.interface import implementer


@implementer(IFieldDeserializer)
@adapter(IJSONField, IBlocks, IExportImportRequestMarker)
class ExportImportBlocksDeserializer(DefaultFieldDeserializer):
"""We skip the subscribers that deserialize the blocks from the frontend.
We only need the raw data.
"""
1 change: 1 addition & 0 deletions src/plone/exportimport/deserializers/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<configure xmlns="http://namespaces.zope.org/zope">
<adapter factory=".blob.ExportImportNamedFieldDeserializer" />
<adapter factory=".blocks.ExportImportBlocksDeserializer" />
</configure>
59 changes: 36 additions & 23 deletions src/plone/exportimport/exporters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
from .base import BaseExporter
from pathlib import Path
from plone import api
from plone.exportimport import interfaces
from plone.exportimport import logger
from plone.exportimport import PACKAGE_NAME
from Products.CMFPlone.Portal import PloneSite
from tempfile import mkdtemp
from typing import Dict
from typing import List
from typing import Optional
from zope.component import getAdapter
from zope.component import hooks
from zope.component import queryAdapter
from zope.component.hooks import setSite
from zope.interface import implementer


@implementer(interfaces.IExporterUtility)
class ExporterUtility:
ExporterMapping = Dict[str, BaseExporter]


@implementer(interfaces.IExporter)
class Exporter:
"""Export content from a Plone Site."""

exporter_names = (
Expand All @@ -24,12 +30,22 @@ class ExporterUtility:
"plone.exporter.translations",
"plone.exporter.discussions",
)
exporters: ExporterMapping

@staticmethod
def _prepare_site(site: PloneSite) -> PloneSite:
"""Use setSite to register the site with global site manager."""
setSite(site)
return site
def __init__(self, site):
self.site = site
self.exporters = self.all_exporters()

def all_exporters(self) -> ExporterMapping:
"""Return all exporters."""
exporters = {}
for exporter_name in self.exporter_names:
exporter = queryAdapter(
self.site, interfaces.INamedExporter, name=exporter_name
)
if exporter:
exporters[exporter_name] = exporter
return exporters

@staticmethod
def _prepare_path(path: Optional[Path] = None) -> Path:
Expand All @@ -43,22 +59,19 @@ def _prepare_path(path: Optional[Path] = None) -> Path:
path = Path(mkdtemp(prefix=PACKAGE_NAME))
return path

def all_exporters(self, site: PloneSite) -> List[BaseExporter]:
"""Return all exporters."""
exporters = {}
for exporter_name in self.exporter_names:
exporter = queryAdapter(site, interfaces.INamedExporter, name=exporter_name)
if exporter:
exporters[exporter_name] = exporter
return exporters

def export_site(self, site: PloneSite, path: Optional[Path] = None) -> List[Path]:
def export_site(self, path: Optional[Path] = None) -> List[Path]:
"""Export the given site to the filesystem."""
site = self._prepare_site(site)
path = self._prepare_path(path)
paths: List[Path] = [path]
all_exporters = self.all_exporters(site)
for exporter_name, exporter in all_exporters.items():
logger.debug(f"Exporting {site} with {exporter_name} to {path}")
paths.extend(exporter.export_data(path))
with hooks.site(self.site):
for exporter_name, exporter in self.exporters.items():
logger.debug(f"Exporting {self.site} with {exporter_name} to {path}")
paths.extend(exporter.export_data(path))
return paths


def get_exporter(site: PloneSite = None) -> Exporter:
"""Get the exporter."""
if site is None:
site = api.portal.get()
return getAdapter(site, interfaces.IExporter)
4 changes: 2 additions & 2 deletions src/plone/exportimport/exporters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def export_data(
if not base_path.exists():
base_path.mkdir(parents=True)
self.base_path = base_path
self.data_hooks = data_hooks if data_hooks else []
self.obj_hooks = obj_hooks if obj_hooks else []
self.data_hooks = self.data_hooks or data_hooks or []
self.obj_hooks = self.obj_hooks or obj_hooks or []
paths = self.dump()
return paths
9 changes: 4 additions & 5 deletions src/plone/exportimport/exporters/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Utility -->
<utility
factory=".ExporterUtility"
provides="plone.exportimport.interfaces.IExporterUtility"
name="plone.exporter"
<!-- Main exporter -->
<adapter
factory=".Exporter"
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
/>

<!-- Exporters -->
Expand Down
10 changes: 5 additions & 5 deletions src/plone/exportimport/exporters/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from plone.exportimport import logger
from plone.exportimport import settings
from plone.exportimport import types
from plone.exportimport.interfaces import IExportImportBlobsMarker
from plone.exportimport.interfaces import IExportImportRequestMarker
from plone.exportimport.utils import content as content_utils
from plone.exportimport.utils import request_provides
from typing import Callable
Expand Down Expand Up @@ -90,9 +90,9 @@ def serialize(self, obj: DexterityContent) -> dict:
# Enrich
for enricher in content_utils.enrichers():
logger.debug(f"{config.logger_prefix} Running {enricher.name}")
addittional = enricher.func(obj, config)
if addittional:
data.update(addittional)
additional = enricher.func(obj, config)
if additional:
data.update(additional)

# Apply data hooks
for func in self.data_hooks:
Expand Down Expand Up @@ -130,7 +130,7 @@ def dump_metadata(self) -> Path:
def dump(self) -> List[Path]:
"""Serialize contents and dump them to disk."""
paths = []
with request_provides(self.request, IExportImportBlobsMarker):
with request_provides(self.request, IExportImportRequestMarker):
for obj in self.all_objects():
path = self.dump_one(obj)
if path:
Expand Down
49 changes: 32 additions & 17 deletions src/plone/exportimport/importers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
from .base import BaseImporter
from pathlib import Path
from plone import api
from plone.exportimport import interfaces
from plone.exportimport import logger
from Products.CMFPlone.Portal import PloneSite
from typing import Dict
from typing import List
from zope.component import getAdapter
from zope.component import hooks
from zope.component import queryAdapter
from zope.component.hooks import setSite
from zope.interface import implementer


@implementer(interfaces.IImporterUtility)
class ImporterUtility:
ImporterMapping = Dict[str, BaseImporter]


@implementer(interfaces.IImporter)
class Importer:
"""Import content into a Plone Site."""

importer_names = (
Expand All @@ -21,28 +27,37 @@ class ImporterUtility:
"plone.importer.translations",
"plone.importer.discussions",
)
importers: ImporterMapping

@staticmethod
def _prepare_site(site: PloneSite) -> PloneSite:
"""Use setSite to register the site with global site manager."""
setSite(site)
return site
def __init__(self, site):
self.site = site
self.importers = self.all_importers()

def all_importers(self, site: PloneSite) -> List[BaseImporter]:
def all_importers(self) -> List[BaseImporter]:
"""Return all importers."""
importers = {}
for importer_name in self.importer_names:
importer = queryAdapter(site, interfaces.INamedImporter, name=importer_name)
importer = queryAdapter(
self.site, interfaces.INamedImporter, name=importer_name
)
if importer:
importers[importer_name] = importer
return importers

def import_site(self, site: PloneSite, path: Path) -> List[str]:
"""Import the given site to the filesystem."""
def import_site(self, path: Path) -> List[str]:
"""Import the given site from the filesystem."""
report = []
site = self._prepare_site(site)
all_importers = self.all_importers(site)
for importer_name, importer in all_importers.items():
logger.debug(f"Importing from {path} to {site} with {importer_name}")
report.append(importer.import_data(path))
with hooks.site(self.site):
for importer_name, importer in self.importers.items():
logger.debug(
f"Importing from {path} to {self.site} with {importer_name}"
)
report.append(importer.import_data(path))
return report


def get_importer(site: PloneSite = None) -> Importer:
"""Get the importer."""
if site is None:
site = api.portal.get()
return getAdapter(site, interfaces.IImporter)
9 changes: 4 additions & 5 deletions src/plone/exportimport/importers/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Utility -->
<utility
factory=".ImporterUtility"
provides="plone.exportimport.interfaces.IImporterUtility"
name="plone.importer"
<!-- Main importer -->
<adapter
factory=".Importer"
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
/>

<!-- Importers -->
Expand Down
Loading

0 comments on commit 7c0258b

Please sign in to comment.