Change CI to GitHub actions

Use copier template from oca/oca-addons-repo-template

Target Python3.8

Apply linting

Fix a missing call to super
Ensure all modules have a 13.0.x.x.x version
This commit is contained in:
Yannick Payot
2023-05-11 11:17:54 +02:00
parent 0000eab313
commit 71da584087
80 changed files with 1596 additions and 879 deletions
+15 -17
View File
@@ -2,20 +2,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
{'name': 'Attachments on Swift storage',
'summary': 'Store assets and attachments on a Swift compatible object store',
'version': '13.0.1.0.0',
'author': 'Camptocamp,Odoo Community Association (OCA)',
'license': 'AGPL-3',
'category': 'Knowledge Management',
'depends': ['base_attachment_object_storage'],
'external_dependencies': {
'python': ['swiftclient',
'keystoneclient',
'keystoneauth1',
],
},
'website': 'https://www.camptocamp.com',
'data': [],
'installable': True,
}
{
"name": "Attachments on Swift storage",
"summary": "Store assets and attachments on a Swift compatible object store",
"version": "13.0.1.0.0",
"author": "Camptocamp,Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Knowledge Management",
"depends": ["base_attachment_object_storage"],
"external_dependencies": {
"python": ["swiftclient", "keystoneclient", "keystoneauth1"],
},
"website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
+46 -47
View File
@@ -5,9 +5,10 @@
import base64
import logging
import os
from ..swift_uri import SwiftUri
from odoo import api, exceptions, models, _
from odoo import _, api, exceptions, models
from ..swift_uri import SwiftUri
_logger = logging.getLogger(__name__)
@@ -49,8 +50,9 @@ class SwiftSessionStore(object):
def _get_key(self, auth_url, username, password, project_name):
return (auth_url, username, password, project_name)
def get_session(self, auth_url=None, username=None, password=None,
project_name=None):
def get_session(
self, auth_url=None, username=None, password=None, project_name=None
):
key = self._get_key(auth_url, username, password, project_name)
session = self._sessions.get(key)
if not session:
@@ -59,13 +61,10 @@ class SwiftSessionStore(object):
password=password,
project_name=project_name,
auth_url=auth_url,
project_domain_id='default',
user_domain_id='default',
)
session = keystoneauth1.session.Session(
auth=auth,
timeout=SWIFT_TIMEOUT,
project_domain_id="default",
user_domain_id="default",
)
session = keystoneauth1.session.Session(auth=auth, timeout=SWIFT_TIMEOUT,)
self._sessions[key] = session
return session
@@ -74,36 +73,38 @@ swift_session_store = SwiftSessionStore()
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
_inherit = "ir.attachment"
def _get_stores(self):
l = ['swift']
l += super()._get_stores()
return l
stores = ["swift"]
stores += super()._get_stores()
return stores
@api.model
def _get_swift_connection(self):
""" Returns a connection object for the Swift object store """
host = os.environ.get('SWIFT_AUTH_URL')
account = os.environ.get('SWIFT_ACCOUNT')
password = os.environ.get('SWIFT_PASSWORD')
project_name = os.environ.get('SWIFT_PROJECT_NAME')
if not project_name and os.environ.get('SWIFT_TENANT_NAME'):
project_name = os.environ['SWIFT_TENANT_NAME']
host = os.environ.get("SWIFT_AUTH_URL")
account = os.environ.get("SWIFT_ACCOUNT")
password = os.environ.get("SWIFT_PASSWORD")
project_name = os.environ.get("SWIFT_PROJECT_NAME")
if not project_name and os.environ.get("SWIFT_TENANT_NAME"):
project_name = os.environ["SWIFT_TENANT_NAME"]
_logger.warning(
"SWIFT_TENANT_NAME is deprecated and "
"must be replaced by SWIFT_PROJECT_NAME"
)
region = os.environ.get('SWIFT_REGION_NAME')
region = os.environ.get("SWIFT_REGION_NAME")
os_options = {}
if region:
os_options['region_name'] = region
os_options["region_name"] = region
if not (host and account and password and project_name):
raise exceptions.UserError(_(
"Problem connecting to Swift store, are the env variables "
"(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
"SWIFT_TENANT_NAME) properly set?"
))
raise exceptions.UserError(
_(
"Problem connecting to Swift store, are the env variables "
"(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
"SWIFT_TENANT_NAME) properly set?"
)
)
try:
session = swift_session_store.get_session(
username=account,
@@ -112,17 +113,16 @@ class IrAttachment(models.Model):
auth_url=host,
)
conn = swiftclient.client.Connection(
session=session,
os_options=os_options,
session=session, os_options=os_options,
)
except ClientException:
_logger.exception('Error connecting to Swift object store')
raise exceptions.UserError(_('Error on Swift connection'))
_logger.exception("Error connecting to Swift object store")
raise exceptions.UserError(_("Error on Swift connection"))
return conn
@api.model
def _store_file_read(self, fname, bin_size=False):
if fname.startswith('swift://'):
if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
try:
conn = self._get_swift_connection()
@@ -130,32 +130,32 @@ class IrAttachment(models.Model):
_logger.exception(
"error reading attachment '%s' from object storage", fname
)
return ''
return ""
try:
resp, obj_content = conn.get_object(swifturi.container(),
swifturi.item())
resp, obj_content = conn.get_object(
swifturi.container(), swifturi.item()
)
read = base64.b64encode(obj_content)
except ClientException:
read = ''
_logger.exception(
'Error reading object from Swift object store')
read = ""
_logger.exception("Error reading object from Swift object store")
return read
else:
return super()._store_file_read(fname, bin_size)
def _store_file_write(self, key, bin_data):
if self._storage() == 'swift':
container = os.environ.get('SWIFT_WRITE_CONTAINER')
if self._storage() == "swift":
container = os.environ.get("SWIFT_WRITE_CONTAINER")
# replace {db} with the database name to handle multi-tenancy
container = container.format(db=self.env.cr.dbname)
conn = self._get_swift_connection()
conn.put_container(container)
filename = 'swift://{}/{}'.format(container, key)
filename = "swift://{}/{}".format(container, key)
try:
conn.put_object(container, key, bin_data)
except ClientException:
_logger.exception('Error writing to Swift object store')
raise exceptions.UserError(_('Error writing to Swift'))
_logger.exception("Error writing to Swift object store")
raise exceptions.UserError(_("Error writing to Swift"))
else:
_super = super()
filename = _super._store_file_write(key, bin_data)
@@ -163,18 +163,17 @@ class IrAttachment(models.Model):
@api.model
def _store_file_delete(self, fname):
if fname.startswith('swift://'):
if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
container = swifturi.container()
# delete the file only if it is on the current configured bucket
# otherwise, we might delete files used on a different environment
if container == os.environ.get('SWIFT_WRITE_CONTAINER'):
if container == os.environ.get("SWIFT_WRITE_CONTAINER"):
conn = self._get_swift_connection()
try:
conn.delete_object(container, swifturi.item())
except ClientException:
_logger.exception(
_('Error deleting an object on the Swift store'))
_logger.exception(_("Error deleting an object on the Swift store"))
# we ignore the error, file will stay on the object
# storage but won't disrupt the process
else:
+1 -2
View File
@@ -6,8 +6,7 @@ import re
class SwiftUri(object):
_url_re = re.compile("^swift:///*([^/]*)/?(.*)",
re.IGNORECASE | re.UNICODE)
_url_re = re.compile("^swift:///*([^/]*)/?(.*)", re.IGNORECASE | re.UNICODE)
def __init__(self, uri):
match = self._url_re.match(uri)
-1
View File
@@ -1,2 +1 @@
from . import test_mock_swift_api
+53 -54
View File
@@ -2,30 +2,27 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import base64
import mock
import os
import keystoneauth1
import mock
from mock import patch
import keystoneauth1
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
from odoo.addons.attachment_swift.models.ir_attachment import SwiftSessionStore
from odoo.addons.attachment_swift.swift_uri import SwiftUri
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
class TestAttachmentSwift(TestIrAttachment):
def setup(self):
super().setUp()
self.env['ir.config_parameter'].set_param('ir_attachment.location',
'swift')
self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
def test_session_store_get_session(self):
auth_url = 'auth_url'
username = 'username'
password = 'password'
project_name = 'project_name'
auth_url = "auth_url"
username = "username"
password = "password"
project_name = "project_name"
store = SwiftSessionStore()
session = store.get_session(
auth_url=auth_url,
@@ -34,10 +31,12 @@ class TestAttachmentSwift(TestIrAttachment):
project_name=project_name,
)
self.assertEqual(session.auth.auth_url, auth_url)
self.assertEqual(session.auth.get_cache_id_elements().get(
'password_username'), username)
self.assertEqual(session.auth.get_cache_id_elements().get(
'password_password'), password)
self.assertEqual(
session.auth.get_cache_id_elements().get("password_username"), username
)
self.assertEqual(
session.auth.get_cache_id_elements().get("password_password"), password
)
self.assertEqual(session.auth.project_name, project_name)
# get the same session on a second call
@@ -48,73 +47,73 @@ class TestAttachmentSwift(TestIrAttachment):
password=password,
project_name=project_name,
),
session
session,
)
@patch('swiftclient.client')
@patch("swiftclient.client")
def test_connection(self, mock_swift_client):
""" Test the connection to the store"""
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
os.environ['SWIFT_ACCOUNT'] = 'account'
os.environ['SWIFT_PASSWORD'] = 'password'
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
os.environ['SWIFT_REGION_NAME'] = 'NOWHERE'
os.environ["SWIFT_AUTH_URL"] = "auth_url"
os.environ["SWIFT_ACCOUNT"] = "account"
os.environ["SWIFT_PASSWORD"] = "password"
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
os.environ["SWIFT_REGION_NAME"] = "NOWHERE"
attachment = self.Attachment
attachment._get_swift_connection()
mock_swift_client.Connection.assert_called_once_with(
session=mock.ANY,
os_options={'region_name': os.environ.get('SWIFT_REGION_NAME')},
os_options={"region_name": os.environ.get("SWIFT_REGION_NAME")},
)
__, kwargs = mock_swift_client.Connection.call_args
session = kwargs['session']
session = kwargs["session"]
self.assertTrue(isinstance(session, keystoneauth1.session.Session))
self.assertEqual(session.auth.auth_url, os.environ['SWIFT_AUTH_URL'])
self.assertEqual(session.auth.get_cache_id_elements().get(
'password_username'), os.environ['SWIFT_ACCOUNT'])
self.assertEqual(session.auth.get_cache_id_elements().get(
'password_password'), os.environ['SWIFT_PASSWORD'])
self.assertEqual(session.auth.project_name,
os.environ['SWIFT_PROJECT_NAME'])
self.assertEqual(session.auth.auth_url, os.environ["SWIFT_AUTH_URL"])
self.assertEqual(
session.auth.get_cache_id_elements().get("password_username"),
os.environ["SWIFT_ACCOUNT"],
)
self.assertEqual(
session.auth.get_cache_id_elements().get("password_password"),
os.environ["SWIFT_PASSWORD"],
)
self.assertEqual(session.auth.project_name, os.environ["SWIFT_PROJECT_NAME"])
def test_store_file_on_swift(self):
"""
Test writing a file
"""
(self.env['ir.config_parameter'].
set_param('ir_attachment.location', 'swift'))
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
os.environ['SWIFT_ACCOUNT'] = 'account'
os.environ['SWIFT_PASSWORD'] = 'password'
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
container = os.environ.get('SWIFT_WRITE_CONTAINER')
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
os.environ["SWIFT_AUTH_URL"] = "auth_url"
os.environ["SWIFT_ACCOUNT"] = "account"
os.environ["SWIFT_PASSWORD"] = "password"
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
container = os.environ.get("SWIFT_WRITE_CONTAINER")
attachment = self.Attachment
bin_data = base64.b64decode(self.blob1_b64)
with patch('swiftclient.client.Connection') as MockConnection:
with patch("swiftclient.client.Connection") as MockConnection:
conn = MockConnection.return_value
attachment.create({'name': 'a5', 'datas': self.blob1_b64})
attachment.create({"name": "a5", "datas": self.blob1_b64})
conn.put_object.assert_called_with(
container,
attachment._compute_checksum(bin_data),
bin_data)
container, attachment._compute_checksum(bin_data), bin_data
)
def test_delete_file_on_swift(self):
"""
Test deleting a file
"""
(self.env['ir.config_parameter'].
set_param('ir_attachment.location', 'swift'))
os.environ['SWIFT_AUTH_URL'] = 'auth_url'
os.environ['SWIFT_ACCOUNT'] = 'account'
os.environ['SWIFT_PASSWORD'] = 'password'
os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
os.environ["SWIFT_AUTH_URL"] = "auth_url"
os.environ["SWIFT_ACCOUNT"] = "account"
os.environ["SWIFT_PASSWORD"] = "password"
os.environ["SWIFT_PROJECT_NAME"] = "project_name"
os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
attachment = self.Attachment
container = os.environ.get('SWIFT_WRITE_CONTAINER')
with patch('swiftclient.client.Connection') as MockConnection:
container = os.environ.get("SWIFT_WRITE_CONTAINER")
with patch("swiftclient.client.Connection") as MockConnection:
conn = MockConnection.return_value
a5 = attachment.create({'name': 'a5', 'datas': self.blob1_b64})
a5 = attachment.create({"name": "a5", "datas": self.blob1_b64})
uri = SwiftUri(a5.store_fname)
a5.unlink()
conn.delete_object.assert_called_with(container, uri.item())
@@ -1,10 +1,12 @@
# Copyright 2017-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
from ..swift_uri import SwiftUri
from swiftclient.exceptions import ClientException
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
from ..swift_uri import SwiftUri
class TestAttachmentSwift(TestIrAttachment):
"""
@@ -13,8 +15,7 @@ class TestAttachmentSwift(TestIrAttachment):
def setup(self):
super().setUp()
self.env['ir.config_parameter'].set_param('ir_attachment.location',
'swift')
self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
def test_connection(self):
""" Test the connection to the Swift object store """
@@ -23,17 +24,15 @@ class TestAttachmentSwift(TestIrAttachment):
def test_store_file_on_swift(self):
""" Test writing a file and then reading it """
(self.env['ir.config_parameter'].
set_param('ir_attachment.location', 'swift'))
a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
a5bis = self.Attachment.browse(a5.id)[0]
self.assertEqual(a5.datas, a5bis.datas)
def test_delete_file_on_swift(self):
""" Create a file and then test the deletion """
(self.env['ir.config_parameter'].
set_param('ir_attachment.location', 'swift'))
a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
(self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
uri = SwiftUri(a5.store_fname)
con = self.Attachment._get_swift_connection()
con.get_object(uri.container(), uri.item())