Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@
target = plone
[console_scripts]
update_locale = redturtle.rsync.locales.update:update_locale
redturtle_rsync = redturtle.rsync.scripts.rsync:main
""",
)
2 changes: 1 addition & 1 deletion src/redturtle/rsync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from zope.i18nmessageid import MessageFactory


_ = MessageFactory('redturtle.rsync')
_ = MessageFactory("redturtle.rsync")
Empty file.
160 changes: 160 additions & 0 deletions src/redturtle/rsync/adapters/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from pathlib import Path
from redturtle.rsync.interfaces import IRedturtleRsyncAdapter
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from zope.component import adapter
from zope.interface import implementer
from zope.interface import Interface

import json
import requests


class TimeoutHTTPAdapter(HTTPAdapter):
def __init__(self, *args, **kwargs):
if "timeout" in kwargs:
self.timeout = kwargs["timeout"]
del kwargs["timeout"]
super(TimeoutHTTPAdapter, self).__init__(*args, **kwargs)

def send(self, request, **kwargs):
timeout = kwargs.get("timeout")
if timeout is None:
kwargs["timeout"] = self.timeout
return super(TimeoutHTTPAdapter, self).send(request, **kwargs)


@implementer(IRedturtleRsyncAdapter)
@adapter(Interface, Interface)
class RsyncAdapterBase:
"""
This is the base class for all rsync adapters.
It provides a common interface for all adapters and some default
implementations of the methods.
Default methods works with some data in restapi-like format.
"""

def __init__(self, context, request):
self.context = context
self.request = request

def requests_retry_session(
self,
retries=3,
backoff_factor=0.3,
status_forcelist=(500, 501, 502, 503, 504),
timeout=5.0,
session=None,
):
"""
https://dev.to/ssbozy/python-requests-with-retries-4p03
"""
session = session or requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
)
# adapter = HTTPAdapter(max_retries=retry)
http_adapter = TimeoutHTTPAdapter(max_retries=retry, timeout=timeout)
session.mount("http://", http_adapter)
session.mount("https://", http_adapter)
return session

def log_item_title(self, start, options):
"""
Return the title of the log item for the rsync command.
"""
return f"Report sync {start.strftime('%d-%m-%Y %H:%M:%S')}"

def set_args(self, parser):
"""
Set some additional arguments for the rsync command.

For example:
parser.add_argument(
"--import-type",
choices=["xxx", "yyy", "zzz"],
help="Import type",
)
"""
return

def get_data(self, options):
"""
Convert the data to be used for the rsync command.
Return:
- data: the data to be used for the rsync command
- error: an error message if there was an error, None otherwise
"""
error = None
data = None
# first, read source data
if getattr(options, "source_path", None):
file_path = Path(options.source_path)
if file_path.exists() and file_path.is_file():
with open(file_path, "r") as f:
try:
data = json.load(f)
except json.JSONDecodeError:
data = f.read()
else:
error = f"Source file not found in: {file_path}"
return data, error
elif getattr(options, "source_url", None):
http = self.requests_retry_session(retries=7, timeout=30.0)
response = http.get(options.source_url)
if response.status_code != 200:
error = f"Error getting data from {options.source_url}: {response.status_code}"
return data, error
if "application/json" in response.headers.get("Content-Type", ""):
try:
data = response.json()
except ValueError:
data = response.content
else:
data = response.content

if data:
data, error = self.convert_source_data(data)
return data, error

def convert_source_data(self, data):
"""
If needed, convert the source data to a format that can be used by the rsync command.
"""
return data, None

def find_item_from_row(self, row):
"""
Find the item in the context from the given row of data.
This method should be implemented by subclasses to find the specific type of content item.
"""
raise NotImplementedError()

def create_item(self, row, options):
"""
Create a new content item from the given row of data.
This method should be implemented by subclasses to create the specific type of content item.
"""
raise NotImplementedError()

def update_item(self, item, row):
"""
Update an existing content item from the given row of data.
This method should be implemented by subclasses to update the specific type of content item.
"""
raise NotImplementedError()

def delete_items(self, data, sync_uids):
"""
params:
- data: the data to be used for the rsync command
- sync_uids: the uids of the items thata has been updated

Delete items if needed.
This method should be implemented by subclasses to delete the specific type of content item.
"""
return
8 changes: 8 additions & 0 deletions src/redturtle/rsync/adapters/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="redturtle.rsync"
>

<adapter factory=".adapter.RsyncAdapterBase" />
</configure>
10 changes: 7 additions & 3 deletions src/redturtle/rsync/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="redturtle.rsync">
i18n_domain="redturtle.rsync"
>

<!-- Set overrides folder for Just-a-Bunch-Of-Templates product -->
<include package="z3c.jbot" file="meta.zcml" />
<include
package="z3c.jbot"
file="meta.zcml"
/>
<browser:jbot
directory="overrides"
layer="redturtle.rsync.interfaces.IRedturtleRsyncLayer"
/>

<!-- Publish static files -->
<plone:static
directory="static"
name="redturtle.rsync"
type="plone"
directory="static"
/>

</configure>
8 changes: 5 additions & 3 deletions src/redturtle/rsync/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="redturtle.rsync">
i18n_domain="redturtle.rsync"
>

<i18n:registerTranslations directory="locales" />

Expand All @@ -16,23 +17,24 @@
<!-- has to be loaded before permissions are used, so keep above views aso. -->
<include file="permissions.zcml" />

<include package=".adapters" />
<include package=".browser" />

<genericsetup:registerProfile
name="default"
title="redturtle.rsync"
directory="profiles/default"
description="Installs the redturtle.rsync add-on."
provides="Products.GenericSetup.interfaces.EXTENSION"
directory="profiles/default"
post_handler=".setuphandlers.post_install"
/>

<genericsetup:registerProfile
name="uninstall"
title="redturtle.rsync (uninstall)"
directory="profiles/uninstall"
description="Uninstalls the redturtle.rsync add-on."
provides="Products.GenericSetup.interfaces.EXTENSION"
directory="profiles/uninstall"
post_handler=".setuphandlers.uninstall"
/>

Expand Down
65 changes: 64 additions & 1 deletion src/redturtle/rsync/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,71 @@
# -*- coding: utf-8 -*-
"""Module where all interfaces, events and exceptions live."""

from zope.interface import Interface
from zope.publisher.interfaces.browser import IDefaultBrowserLayer


class IRedturtleRsyncLayer(IDefaultBrowserLayer):
"""Marker interface that defines a browser layer."""


class IRedturtleRsyncAdapter(Interface):
"""Marker interface for the redturtle rsync adapter."""

def __init__(context, request):
"""Initialize the adapter with the given context and request."""

def log_item_title(start, options):
"""
Return the title of the log item for the rsync command.
"""

def set_args(parser):
"""
Set some additional arguments for the rsync command.
"""

def get_data(options):
"""
Set some additional arguments for the rsync command.
"""

def handle_row(row):
"""
Method to handle a row of data.
For example it could do the following steps:
- check if there is already a content item with the same id
- if not, create a new content item
- if yes, update the existing content item

It should return the content item created or updated and the status of the operation.
The status could be one of the following:
- "created": a new content item was created
- "updated": an existing content item was updated
- "skipped": the content item was skipped because it already exists and is up to date
- "error": an error occurred while processing the content item

for example:
return {'item': content_item, 'status': status}
"""

def create_item(row):
"""
Create a new content item from the given row of data.
This method should be implemented by subclasses to create the specific type of content item.
"""

def update_item(item, row):
"""
Update an existing content item from the given row of data.
This method should be implemented by subclasses to update the specific type of content item.
"""

def delete_items(data, sync_uids):
"""
params:
- data: the data to be used for the rsync command
- sync_uids: the uids of the items thata has been updated

Delete items if needed.
This method should be implemented by subclasses to delete the specific type of content item.
"""
26 changes: 13 additions & 13 deletions src/redturtle/rsync/locales/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@
import subprocess


domain = 'redturtle.rsync'
os.chdir(pkg_resources.resource_filename(domain, ''))
os.chdir('../../../')
target_path = 'src/redturtle/rsync/'
locale_path = target_path + 'locales/'
i18ndude = './bin/i18ndude'
domain = "redturtle.rsync"
os.chdir(pkg_resources.resource_filename(domain, ""))
os.chdir("../../../")
target_path = "src/redturtle/rsync/"
locale_path = target_path + "locales/"
i18ndude = "./bin/i18ndude"

# ignore node_modules files resulting in errors
excludes = '"*.html *json-schema*.xml"'


def locale_folder_setup():
os.chdir(locale_path)
languages = [d for d in os.listdir('.') if os.path.isdir(d)]
languages = [d for d in os.listdir(".") if os.path.isdir(d)]
for lang in languages:
folder = os.listdir(lang)
if 'LC_MESSAGES' in folder:
if "LC_MESSAGES" in folder:
continue
else:
lc_messages_path = lang + '/LC_MESSAGES/'
lc_messages_path = lang + "/LC_MESSAGES/"
os.mkdir(lc_messages_path)
cmd = 'msginit --locale={0} --input={1}.pot --output={2}/LC_MESSAGES/{3}.po'.format( # NOQA: E501
cmd = "msginit --locale={0} --input={1}.pot --output={2}/LC_MESSAGES/{3}.po".format( # NOQA: E501
lang,
domain,
lang,
Expand All @@ -37,11 +37,11 @@ def locale_folder_setup():
shell=True,
)

os.chdir('../../../../')
os.chdir("../../../../")


def _rebuild():
cmd = '{i18ndude} rebuild-pot --pot {locale_path}/{domain}.pot --exclude {excludes} --create {domain} {target_path}'.format( # NOQA: E501
cmd = "{i18ndude} rebuild-pot --pot {locale_path}/{domain}.pot --exclude {excludes} --create {domain} {target_path}".format( # NOQA: E501
i18ndude=i18ndude,
locale_path=locale_path,
domain=domain,
Expand All @@ -55,7 +55,7 @@ def _rebuild():


def _sync():
cmd = '{0} sync --pot {1}/{2}.pot {3}*/LC_MESSAGES/{4}.po'.format(
cmd = "{0} sync --pot {1}/{2}.pot {3}*/LC_MESSAGES/{4}.po".format(
i18ndude,
locale_path,
domain,
Expand Down
9 changes: 5 additions & 4 deletions src/redturtle/rsync/permissions.zcml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="plone">
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="plone"
>

<configure zcml:condition="installed AccessControl.security">
<!-- -*- extra stuff goes here -*- -->
<!-- -*- extra stuff goes here -*- -->


</configure>
Expand Down
Loading
Loading