From 06193cb0b8c7d9bd599f5e8198912e64a0499836 Mon Sep 17 00:00:00 2001 From: kmbhm1 Date: Mon, 5 Aug 2024 18:11:47 -0500 Subject: [PATCH] feat: update version logic to dts and latest schema (#39) * feat: add ability to inherit from all-null parent classes in pydantic fastapi models * feat: change versioning to datetime --- supabase_pydantic/cli.py | 17 ++++++++------- .../util/writers/abstract_classes.py | 16 ++++++++++---- supabase_pydantic/util/writers/util.py | 21 +++++++++++-------- tests/unit/writers/test_abstract_classes.py | 7 ++++--- tests/unit/writers/test_writer_util.py | 13 +++++++++++- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/supabase_pydantic/cli.py b/supabase_pydantic/cli.py index 8a1e370..15e3e91 100644 --- a/supabase_pydantic/cli.py +++ b/supabase_pydantic/cli.py @@ -183,7 +183,14 @@ def clean(ctx: Any, directory: str) -> None: help='The directory to save files', required=False, ) -@click.option('--overwrite/--no-overwrite', default=True, help='Overwrite existing files. Defaults to overwrite.') +@click.option( + '--no-overwrite', + 'overwrite', + is_flag=True, + show_default=True, + default=False, + help='Overwrite existing files. Defaults to overwrite.', +) @click.option( '--null-parent-classes', is_flag=True, @@ -204,10 +211,6 @@ def gen( ) -> None: """Generate models from a PostgreSQL database.""" # pp.pprint(locals()) - # if dburl is None and project_id is None and not local and not linked: - # print('Please provide a connection source. Exiting...') - # return - # Load environment variables from .env file & check if they are set correctly if not local and db_url is None: print('Please provide a connection source. Exiting...') @@ -256,10 +259,10 @@ def gen( factory = FileWriterFactory() for job, c in jobs.items(): # c = config print(f'Generating {job} models...') - p = factory.get_file_writer( + p, vf = factory.get_file_writer( tables, c.fpath(), c.file_type, c.framework_type, add_null_parent_classes=null_parent_classes ).save(overwrite) - paths.append(p) + paths += [p, vf] if vf is not None else [p] print(f'{job} models generated successfully: {p}') # Format the generated files diff --git a/supabase_pydantic/util/writers/abstract_classes.py b/supabase_pydantic/util/writers/abstract_classes.py index 347c696..5cad266 100644 --- a/supabase_pydantic/util/writers/abstract_classes.py +++ b/supabase_pydantic/util/writers/abstract_classes.py @@ -1,3 +1,4 @@ +import os from abc import ABC, abstractmethod from pathlib import Path @@ -109,15 +110,22 @@ def write(self) -> str: # filter None and join parts return self.jstr.join(p for p in parts if p is not None) + '\n' - def save(self, overwrite: bool = False) -> str: + def save(self, overwrite: bool = False) -> tuple[str, str | None]: """Method to save the file.""" fp = Path(self.file_path) base, ext, directory = fp.stem, fp.suffix, str(fp.parent) - p = generate_unique_filename(base, ext, directory) if not overwrite and fp.exists() else self.file_path - with open(p, 'w') as f: + latest_file = os.path.join(directory, f'{base}_latest{ext}') + with open(latest_file, 'w') as f: f.write(self.write()) - return p + if overwrite: + versioned_file = generate_unique_filename(base, ext, directory) + with open(versioned_file, 'w') as f: + f.write(self.write()) + + return latest_file, versioned_file + + return latest_file, None def join(self, strings: list[str]) -> str: """Method to join strings.""" diff --git a/supabase_pydantic/util/writers/util.py b/supabase_pydantic/util/writers/util.py index 216ecc1..caf131b 100644 --- a/supabase_pydantic/util/writers/util.py +++ b/supabase_pydantic/util/writers/util.py @@ -1,4 +1,5 @@ import os +from datetime import datetime, timezone from supabase_pydantic.util.constants import BASE_CLASS_POSTFIX, WriterClassType from supabase_pydantic.util.util import chunk_text @@ -23,15 +24,17 @@ def generate_unique_filename(base_name: str, extension: str, directory: str = '. """ extension = extension.lstrip('.') - file_name = f'{base_name}.{extension}' - file_path = os.path.join(directory, file_name) - i = 1 - while os.path.exists(file_path): - file_name = f'{base_name}_{i}.{extension}' - file_path = os.path.join(directory, file_name) - i += 1 - - return file_path + dt_str = datetime.now(tz=timezone.utc).strftime('%Y%m%d%H%M%S%f') + file_name = f'{base_name}_{dt_str}.{extension}' + + # file_path = os.path.join(directory, file_name) + # i = 1 + # while os.path.exists(file_path): + # file_name = f'{base_name}_{i}.{extension}' + # file_path = os.path.join(directory, file_name) + # i += 1 + + return os.path.join(directory, file_name) def get_section_comment(comment_title: str, notes: list[str] | None = None) -> str: diff --git a/tests/unit/writers/test_abstract_classes.py b/tests/unit/writers/test_abstract_classes.py index 84dc1f6..80903f8 100644 --- a/tests/unit/writers/test_abstract_classes.py +++ b/tests/unit/writers/test_abstract_classes.py @@ -190,13 +190,14 @@ def test_save_method(): result = writer.save(overwrite=False) # Assert the generate_unique_filename was called correctly - mock_unique_filename.assert_called_once_with('test_file', '.py', 'directory') + # mock_unique_filename.assert_called_once_with('test_file', '.py', 'directory') + mock_unique_filename.assert_not_called() # Assert the file was opened with the correct filename and mode - mock_open.assert_called_once_with('test_file_unique.py', 'w') + mock_open.assert_called_once_with('directory/test_file_latest.py', 'w') # Assert the correct path is returned - assert result == 'test_file_unique.py' + assert result == ('directory/test_file_latest.py', None) def test_abstract_file_writer_type_error_on_implementation(): diff --git a/tests/unit/writers/test_writer_util.py b/tests/unit/writers/test_writer_util.py index 7b66e31..bfaf6e1 100644 --- a/tests/unit/writers/test_writer_util.py +++ b/tests/unit/writers/test_writer_util.py @@ -1,3 +1,5 @@ +import datetime +import re import pytest from unittest.mock import patch from supabase_pydantic.util.constants import WriterClassType @@ -28,7 +30,16 @@ def test_generate_unique_filename(): with patch('os.path.exists', side_effect=lambda x: x.endswith('test.py') or x.endswith('test_1.py')) as mock_exists: unique_filename = generate_unique_filename(base_name, extension, directory) - assert unique_filename == '/fake/directory/test_2.py' + match = re.search(r'test_(\d{20})\.py$', unique_filename) + assert match is not None, f'Filename does not match the expected pattern: {unique_filename}' + + # Validate the datetime format + # Assuming the format is YYYYMMDDHHMMSSffffffffff (year, month, day, hour, minute, second, microsecond) + datetime_str = match.group(1) + try: + datetime.datetime.strptime(datetime_str, '%Y%m%d%H%M%S%f') + except ValueError: + assert False, 'Datetime format is incorrect' def test_get_section_comment_without_notes():