From 16e70020b06bc559f7ed3de7e0eb72e86dc06a05 Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:16:30 +0000 Subject: [PATCH 1/6] #835 Add ruff rule CPY and copyright regex --- ruff.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index 5601bb90f..8e9263ab0 100644 --- a/ruff.toml +++ b/ruff.toml @@ -6,6 +6,10 @@ select = [ #pycodestyle "E", "W", + "CPY" ] ignore = [] -preview = true \ No newline at end of file +preview = true + +[lint.flake8-copyright] +notice-rgx = '^(#!/usr/bin/env python3\n)?# \(C\) British Crown Copyright 20\d{2}(-20\d{2})?, Met Office\.\n# Please see LICENSE\.md for license details\.' From 98684acc05aab54682709a6fe89e75015d13b63c Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:21:30 +0000 Subject: [PATCH 2/6] #835 Remove redundant shebang lines --- cdds/cdds/deprecated/transfer/list_queue.py | 1 - cdds/cdds/deprecated/transfer/resend_failed_msgs.py | 1 - 2 files changed, 2 deletions(-) diff --git a/cdds/cdds/deprecated/transfer/list_queue.py b/cdds/cdds/deprecated/transfer/list_queue.py index e7434e7f2..296625994 100755 --- a/cdds/cdds/deprecated/transfer/list_queue.py +++ b/cdds/cdds/deprecated/transfer/list_queue.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3.6 # (C) British Crown Copyright 2021-2025, Met Office. # Please see LICENSE.md for license details. """Print a list of messages in the CMIP6 queues""" diff --git a/cdds/cdds/deprecated/transfer/resend_failed_msgs.py b/cdds/cdds/deprecated/transfer/resend_failed_msgs.py index 01ff5957f..767c04ddc 100644 --- a/cdds/cdds/deprecated/transfer/resend_failed_msgs.py +++ b/cdds/cdds/deprecated/transfer/resend_failed_msgs.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3.6 # (C) British Crown Copyright 2021-2025, Met Office. # Please see LICENSE.md for license details. From 88e873c4cddb83e19d501c20259a8a919d209ecc Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:23:46 +0000 Subject: [PATCH 3/6] #835 Remove old copyright unittest --- cdds/cdds/tests/test_coding_standards.py | 68 ------------------- .../tests/test_coding_standards.py | 67 ------------------ 2 files changed, 135 deletions(-) delete mode 100644 cdds/cdds/tests/test_coding_standards.py delete mode 100644 mip_convert/mip_convert/tests/test_coding_standards.py diff --git a/cdds/cdds/tests/test_coding_standards.py b/cdds/cdds/tests/test_coding_standards.py deleted file mode 100644 index f379b7600..000000000 --- a/cdds/cdds/tests/test_coding_standards.py +++ /dev/null @@ -1,68 +0,0 @@ -# (C) British Crown Copyright 2022-2025, Met Office. -# Please see LICENSE.md for license details. -# pylint: disable = missing-docstring, invalid-name, too-many-public-methods -"""Tests for coding standards and copyright headers.""" -import os -import re -import unittest -import pytest -from pathlib import Path - -import cdds - -COPYRIGHT_TEMPLATE = ('{start_comment} (C) British Crown Copyright {years}, Met Office.' - '\n{start_comment} Please see LICENSE.md for license details.') - - -@pytest.mark.style -class TestCodingStandards(unittest.TestCase): - """Tests for coding standards.""" - - def setUp(self): - cdds_dir = Path(cdds.__file__).parent.absolute() - self.all_files = [ - os.path.join(dir, filename) for dir, _, filenames in os.walk(cdds_dir) for filename in filenames - ] - self.exclude_patterns = ['conf.py'] - - def test_copyright_headers(self): - # Add optional shebang. - copyright_format = r'((\#\!.*)\n)?' + re.escape(COPYRIGHT_TEMPLATE) - copyright_format = copyright_format.replace(r'\{start_comment\}', r'(\#|\.{2}|\-{2})') - copyright_format = copyright_format.replace(r'\{years\}', r'(.*?)') - copyright_pattern = re.compile(copyright_format) - - copyright_files = self.get_copyright_files() - matched = True - for full_path in copyright_files: - match = None - with open(full_path, 'r') as file_handler: - match = copyright_pattern.match(file_handler.read()) - if not match: - matched = False - print(('{full_path}: Missing or incorrect formatting of copyright notice'.format(full_path=full_path))) - self.assertTrue(matched, 'There were license header failures') - - def get_copyright_files(self): - self.exclude_patterns.extend( - ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'cfg', 'clyc', - 'pylintrc', 'TAGS', 'json', 'todel', 'nfsc', 'txt', 'ini', 'conf', 'workflows'] - ) - - return [ - filename for filename in self.all_files if True not in set( - [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] - ) - ] - - def get_py_files(self): - return [ - filename for filename in self.all_files if - filename.endswith('.py') and True not in set( - [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] - ) - ] - - -if __name__ == '__main__': - unittest.main() diff --git a/mip_convert/mip_convert/tests/test_coding_standards.py b/mip_convert/mip_convert/tests/test_coding_standards.py deleted file mode 100644 index 427a0e7d1..000000000 --- a/mip_convert/mip_convert/tests/test_coding_standards.py +++ /dev/null @@ -1,67 +0,0 @@ -# (C) British Crown Copyright 2023-2025, Met Office. -# Please see LICENSE.md for license details. -# pylint: disable = missing-docstring, invalid-name, too-many-public-methods -"""Tests for coding standards and copyright headers.""" -import os -import re -import unittest -import pytest -from pathlib import Path - -import mip_convert - -COPYRIGHT_TEMPLATE = ('{start_comment} (C) British Crown Copyright {years}, Met Office.' - '\n{start_comment} Please see LICENSE.md for license details.') - - -@pytest.mark.style -class TestCodingStandards(unittest.TestCase): - """Tests for coding standards.""" - - def setUp(self): - mip_convert_dir = Path(mip_convert.__file__).parent.absolute() - self.all_files = [ - os.path.join(dir, filename) for dir, _, filenames in os.walk(mip_convert_dir) for filename in filenames - ] - self.exclude_patterns = ['conf.py'] - - def test_copyright_headers(self): - # Add optional shebang. - copyright_format = r'((\#\!.*)\n)?' + re.escape(COPYRIGHT_TEMPLATE) - copyright_format = copyright_format.replace(r'\{start_comment\}', r'(\#|\.{2}|\-{2})') - copyright_format = copyright_format.replace(r'\{years\}', r'(.*?)') - copyright_pattern = re.compile(copyright_format) - - copyright_files = self.get_copyright_files() - matched = True - for full_path in copyright_files: - match = None - with open(full_path, 'r') as file_handler: - match = copyright_pattern.match(file_handler.read()) - if not match: - matched = False - print(('{full_path}: Missing or incorrect formatting of copyright notice'.format(full_path=full_path))) - self.assertTrue(matched, 'There were license header failures') - - def get_copyright_files(self): - self.exclude_patterns.extend( - ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'pylintrc', 'TAGS', 'json', 'todel', 'nfsc'] - ) - - return [ - filename for filename in self.all_files if True not in set( - [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] - ) - ] - - def get_py_files(self): - return [ - filename for filename in self.all_files if - filename.endswith('.py') and True not in set( - [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] - ) - ] - - -if __name__ == '__main__': - unittest.main() From a2a0d52532de1fd468aa01f692792716bd600858 Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:27:33 +0000 Subject: [PATCH 4/6] Revert "#835 Remove old copyright unittest" This reverts commit 88e873c4cddb83e19d501c20259a8a919d209ecc. --- cdds/cdds/tests/test_coding_standards.py | 68 +++++++++++++++++++ .../tests/test_coding_standards.py | 67 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 cdds/cdds/tests/test_coding_standards.py create mode 100644 mip_convert/mip_convert/tests/test_coding_standards.py diff --git a/cdds/cdds/tests/test_coding_standards.py b/cdds/cdds/tests/test_coding_standards.py new file mode 100644 index 000000000..f379b7600 --- /dev/null +++ b/cdds/cdds/tests/test_coding_standards.py @@ -0,0 +1,68 @@ +# (C) British Crown Copyright 2022-2025, Met Office. +# Please see LICENSE.md for license details. +# pylint: disable = missing-docstring, invalid-name, too-many-public-methods +"""Tests for coding standards and copyright headers.""" +import os +import re +import unittest +import pytest +from pathlib import Path + +import cdds + +COPYRIGHT_TEMPLATE = ('{start_comment} (C) British Crown Copyright {years}, Met Office.' + '\n{start_comment} Please see LICENSE.md for license details.') + + +@pytest.mark.style +class TestCodingStandards(unittest.TestCase): + """Tests for coding standards.""" + + def setUp(self): + cdds_dir = Path(cdds.__file__).parent.absolute() + self.all_files = [ + os.path.join(dir, filename) for dir, _, filenames in os.walk(cdds_dir) for filename in filenames + ] + self.exclude_patterns = ['conf.py'] + + def test_copyright_headers(self): + # Add optional shebang. + copyright_format = r'((\#\!.*)\n)?' + re.escape(COPYRIGHT_TEMPLATE) + copyright_format = copyright_format.replace(r'\{start_comment\}', r'(\#|\.{2}|\-{2})') + copyright_format = copyright_format.replace(r'\{years\}', r'(.*?)') + copyright_pattern = re.compile(copyright_format) + + copyright_files = self.get_copyright_files() + matched = True + for full_path in copyright_files: + match = None + with open(full_path, 'r') as file_handler: + match = copyright_pattern.match(file_handler.read()) + if not match: + matched = False + print(('{full_path}: Missing or incorrect formatting of copyright notice'.format(full_path=full_path))) + self.assertTrue(matched, 'There were license header failures') + + def get_copyright_files(self): + self.exclude_patterns.extend( + ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'cfg', 'clyc', + 'pylintrc', 'TAGS', 'json', 'todel', 'nfsc', 'txt', 'ini', 'conf', 'workflows'] + ) + + return [ + filename for filename in self.all_files if True not in set( + [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] + ) + ] + + def get_py_files(self): + return [ + filename for filename in self.all_files if + filename.endswith('.py') and True not in set( + [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] + ) + ] + + +if __name__ == '__main__': + unittest.main() diff --git a/mip_convert/mip_convert/tests/test_coding_standards.py b/mip_convert/mip_convert/tests/test_coding_standards.py new file mode 100644 index 000000000..427a0e7d1 --- /dev/null +++ b/mip_convert/mip_convert/tests/test_coding_standards.py @@ -0,0 +1,67 @@ +# (C) British Crown Copyright 2023-2025, Met Office. +# Please see LICENSE.md for license details. +# pylint: disable = missing-docstring, invalid-name, too-many-public-methods +"""Tests for coding standards and copyright headers.""" +import os +import re +import unittest +import pytest +from pathlib import Path + +import mip_convert + +COPYRIGHT_TEMPLATE = ('{start_comment} (C) British Crown Copyright {years}, Met Office.' + '\n{start_comment} Please see LICENSE.md for license details.') + + +@pytest.mark.style +class TestCodingStandards(unittest.TestCase): + """Tests for coding standards.""" + + def setUp(self): + mip_convert_dir = Path(mip_convert.__file__).parent.absolute() + self.all_files = [ + os.path.join(dir, filename) for dir, _, filenames in os.walk(mip_convert_dir) for filename in filenames + ] + self.exclude_patterns = ['conf.py'] + + def test_copyright_headers(self): + # Add optional shebang. + copyright_format = r'((\#\!.*)\n)?' + re.escape(COPYRIGHT_TEMPLATE) + copyright_format = copyright_format.replace(r'\{start_comment\}', r'(\#|\.{2}|\-{2})') + copyright_format = copyright_format.replace(r'\{years\}', r'(.*?)') + copyright_pattern = re.compile(copyright_format) + + copyright_files = self.get_copyright_files() + matched = True + for full_path in copyright_files: + match = None + with open(full_path, 'r') as file_handler: + match = copyright_pattern.match(file_handler.read()) + if not match: + matched = False + print(('{full_path}: Missing or incorrect formatting of copyright notice'.format(full_path=full_path))) + self.assertTrue(matched, 'There were license header failures') + + def get_copyright_files(self): + self.exclude_patterns.extend( + ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'pylintrc', 'TAGS', 'json', 'todel', 'nfsc'] + ) + + return [ + filename for filename in self.all_files if True not in set( + [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] + ) + ] + + def get_py_files(self): + return [ + filename for filename in self.all_files if + filename.endswith('.py') and True not in set( + [exclude_pattern in filename for exclude_pattern in self.exclude_patterns] + ) + ] + + +if __name__ == '__main__': + unittest.main() From fe33b1dd239b219cf11ff884805b410ffde1c02b Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:59:25 +0000 Subject: [PATCH 5/6] #835 Rename tests --- .../tests/{test_coding_standards.py => test_copyright_headers.py} | 0 .../tests/{test_coding_standards.py => test_copyright_headers.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cdds/cdds/tests/{test_coding_standards.py => test_copyright_headers.py} (100%) rename mip_convert/mip_convert/tests/{test_coding_standards.py => test_copyright_headers.py} (100%) diff --git a/cdds/cdds/tests/test_coding_standards.py b/cdds/cdds/tests/test_copyright_headers.py similarity index 100% rename from cdds/cdds/tests/test_coding_standards.py rename to cdds/cdds/tests/test_copyright_headers.py diff --git a/mip_convert/mip_convert/tests/test_coding_standards.py b/mip_convert/mip_convert/tests/test_copyright_headers.py similarity index 100% rename from mip_convert/mip_convert/tests/test_coding_standards.py rename to mip_convert/mip_convert/tests/test_copyright_headers.py From f25d94922d4993a383c5048345a1ecbcdb55adcc Mon Sep 17 00:00:00 2001 From: Jared Drayton <68954395+mo-jareddrayton@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:16:41 +0000 Subject: [PATCH 6/6] #835 Rename tests --- cdds/cdds/tests/test_copyright_headers.py | 10 +++++----- .../mip_convert/tests/test_copyright_headers.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cdds/cdds/tests/test_copyright_headers.py b/cdds/cdds/tests/test_copyright_headers.py index f379b7600..ce5c53102 100644 --- a/cdds/cdds/tests/test_copyright_headers.py +++ b/cdds/cdds/tests/test_copyright_headers.py @@ -1,7 +1,7 @@ -# (C) British Crown Copyright 2022-2025, Met Office. +# (C) British Crown Copyright 2022-2026, Met Office. # Please see LICENSE.md for license details. # pylint: disable = missing-docstring, invalid-name, too-many-public-methods -"""Tests for coding standards and copyright headers.""" +"""Test for copyright headers.""" import os import re import unittest @@ -15,8 +15,8 @@ @pytest.mark.style -class TestCodingStandards(unittest.TestCase): - """Tests for coding standards.""" +class TestCopyrightHeaders(unittest.TestCase): + """Test for copyright headers.""" def setUp(self): cdds_dir = Path(cdds.__file__).parent.absolute() @@ -45,7 +45,7 @@ def test_copyright_headers(self): def get_copyright_files(self): self.exclude_patterns.extend( - ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'cfg', 'clyc', + ['egg-info', 'EGG-INFO', 'dist', '.pyc', 'doctrees', 'html', 'clyc', 'pylintrc', 'TAGS', 'json', 'todel', 'nfsc', 'txt', 'ini', 'conf', 'workflows'] ) diff --git a/mip_convert/mip_convert/tests/test_copyright_headers.py b/mip_convert/mip_convert/tests/test_copyright_headers.py index 427a0e7d1..8c2b76378 100644 --- a/mip_convert/mip_convert/tests/test_copyright_headers.py +++ b/mip_convert/mip_convert/tests/test_copyright_headers.py @@ -1,7 +1,7 @@ -# (C) British Crown Copyright 2023-2025, Met Office. +# (C) British Crown Copyright 2023-2026, Met Office. # Please see LICENSE.md for license details. # pylint: disable = missing-docstring, invalid-name, too-many-public-methods -"""Tests for coding standards and copyright headers.""" +"""Test for copyright headers.""" import os import re import unittest @@ -15,8 +15,8 @@ @pytest.mark.style -class TestCodingStandards(unittest.TestCase): - """Tests for coding standards.""" +class TestCopyrightHeaders(unittest.TestCase): + """Test for copyright headers.""" def setUp(self): mip_convert_dir = Path(mip_convert.__file__).parent.absolute()