From 94962af6d8eb3af69ca04584eda67108e0b87c27 Mon Sep 17 00:00:00 2001 From: abondar Date: Sun, 28 Apr 2024 16:02:10 +0300 Subject: [PATCH] Improved docs --- .github/workflows/gh-pages.yml | 6 ++--- CHANGELOG.rst | 5 ++++ README.rst | 1 + django_copyist/copy_request.py | 10 ++++---- django_copyist/copyist.py | 17 +++++++++++++- docs/source/overview.rst | 32 ++++++++++++------------- docs/source/quickstart.rst | 43 +++++++++++++++++----------------- pyproject.toml | 2 +- 8 files changed, 69 insertions(+), 47 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 187343f..806af76 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -3,9 +3,9 @@ permissions: id-token: write pages: write on: - push: - branches: - - main + release: + types: + - created jobs: build: runs-on: ubuntu-latest diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 578b51b..f3cc976 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,12 @@ Changelog 0.1 === +0.1.3 +----- +- Improved docs + 0.1.1 +----- - Specified python >=3.11 diff --git a/README.rst b/README.rst index 28fe8af..4a3c63f 100644 --- a/README.rst +++ b/README.rst @@ -313,6 +313,7 @@ even if there are issues with matching objects in origin location with objects i It is not used in this example, but you can read more about it in overview section of this documentation. 5. We execute the copy request. + .. code-block:: python result = Copyist(copy_request).execute_copy_request() diff --git a/django_copyist/copy_request.py b/django_copyist/copy_request.py index c2a49c6..d8774b6 100644 --- a/django_copyist/copy_request.py +++ b/django_copyist/copy_request.py @@ -66,16 +66,16 @@ class CopyResult: """ This is the base class for a copy result, which serves as the output for the Copyist. - :param is_copy_successful: A flag indicating whether the copy operation was successful, + :ivar is_copy_successful: A flag indicating whether the copy operation was successful, defaults to False. :type is_copy_successful: bool, optional - :param output_map: A dictionary that contains a mapping of model names to mappings of + :ivar output_map: A dictionary that contains a mapping of model names to mappings of primary keys in the source and destination databases, defaults to None. :type output_map: dict, optional - :param ignored_map: A dictionary that contains a mapping of model names to lists of + :ivar ignored_map: A dictionary that contains a mapping of model names to lists of primary keys of models that were ignored during the copying process, defaults to None. :type ignored_map: dict, optional - :param set_to_filter_map: A dictionary that contains data of substitutes matched by + :ivar set_to_filter_map: A dictionary that contains data of substitutes matched by the `SET_TO_FILTER` action. The structure is as follows: .. code-block:: python @@ -90,7 +90,7 @@ class CopyResult: Defaults to None. :type set_to_filter_map: dict, optional - :param reason: The reason code, returned if `is_copy_successful` is False, defaults to None. + :ivar reason: The reason code, returned if `is_copy_successful` is False, defaults to None. :type reason: AbortReason, optional """ diff --git a/django_copyist/copyist.py b/django_copyist/copyist.py index 89a33fc..2f5a54f 100644 --- a/django_copyist/copyist.py +++ b/django_copyist/copyist.py @@ -88,7 +88,9 @@ def __repr__(self): @dataclass class CopyistConfig: """ - Root copy config, containing ModelCopyConfig list + This is the root copy configuration that contains a list of ModelCopyConfig. + + :ivar list[ModelCopyConfig] model_configs: The list of model configurations. """ model_configs: list[ModelCopyConfig] @@ -105,6 +107,19 @@ class IgnoreEvaluation: class Copyist: + """ + The main class responsible for copying Django model instances based on the provided + configuration. + + :param CopyRequest copy_request: The copy request object containing the configuration and + input data for the copy operation. + + :ivar CopyRequest request: The copy request object containing the configuration + and input data for the copy operation. + :ivar CopyistConfig config: The configuration for the copy operation. + :ivar dict input_data: The input data for the copy operation. + """ + def __init__(self, copy_request: CopyRequest): self.request = copy_request self.config = copy_request.config diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 1511ba3..8b48e27 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -234,7 +234,7 @@ This way you can first copy all parent models, and then use compound actions to Closer look at CopyRequest and CopyResult ----------------------------------------- -You probably noticed the :py:attr:`.CopyRequest` and :py:attr:`.CopyResult` classes that are used in the examples above. Let's take a closer look at them. +You probably noticed the :py:class:`~.CopyRequest` and :py:class:`~.CopyResult` classes that are used in the examples above. Let's take a closer look at them. .. code-block:: python @@ -244,28 +244,28 @@ You probably noticed the :py:attr:`.CopyRequest` and :py:attr:`.CopyResult` clas confirm_write=False, ) -In this example, we create a :py:attr:`.CopyRequest` object. +In this example, we create a :py:class:`~.CopyRequest` object. -:py:attr:`.CopyistConfig` is a class that holds the configuration for the copy process. It takes a list of :py:attr:`.ModelCopyConfig` objects. -It is root config and can have multiple :py:attr:`.ModelCopyConfig` objects if you need to copy several root level models in one request. +:py:class:`~.CopyistConfig` is a class that holds the configuration for the copy process. It takes a list of :py:class:`~.ModelCopyConfig` objects. +It is root config and can have multiple :py:class:`~.ModelCopyConfig` objects if you need to copy several root level models in one request. :py:attr:`.input_data` is a dictionary that holds the input data for the copy process. It is used to pass data to the copy process. It can be used to pass data that is not present in the original model. :py:attr:`.confirm_write` is a more confusing one. It is a boolean that tells the copy process if it should write the data even if unmatched or ignored values were discovered during the copy process. -What are unmatched or ignored values? Let's take a look at the :py:attr:`.CopyResult` object. +What are unmatched or ignored values? Let's take a look at the :py:class:`~.CopyResult` object. -:py:attr:`.CopyResult` is an object that holds the result of the copy process. +:py:class:`~.CopyResult` is an object that holds the result of the copy process. Primarily you should look at attribute :py:attr:`.is_copy_successful`. It is a boolean that tells you if the copy process was successful. If it is `False` you should look at the :py:attr:`.reason` attribute. It is a enum that tells you why the copy process failed. :py:attr:`.output_map` is a dictionary that holds the mapping of the original model id to the new model id. It can be stored for historical purposes or to be used for UI rendering. This field is populated only on successful copy. -If you copy is unsuccessful, you can look at the :py:attr:`.django_copyist.config.CopyResult.set_to_filter_map` and -:py:attr:`.django_copyist.config.CopyResult.ignored_map` attributes. +If you copy is unsuccessful, you can look at the :py:attr:`~django_copyist.config.CopyResult.set_to_filter_map` and +:py:attr:`~django_copyist.config.CopyResult.ignored_map` attributes. They are dictionaries that hold the mapping of the original model id -to matched ids on :py:attr:`.django_copyist.config.CopyResult.set_to_filter_map` -and ignored fields on :py:attr:`.SET_TO_FILTER` action or :py:attr:`.django_copyist.config.ModelCopyConfig.ignore_condition` respectively. +to matched ids on :py:attr:`~django_copyist.config.CopyResult.set_to_filter_map` +and ignored fields on :py:attr:`.SET_TO_FILTER` action or :py:attr:`~django_copyist.config.ModelCopyConfig.ignore_condition` respectively. Why would you use this attributes? Let's see following examples @@ -471,17 +471,17 @@ Above example is great and works well, but what if destination project doesn't h Here we are working with the same config as in the previous example, but now we have `Counterpart` with `external_id` 2 only in the `Project1` and not in the `Project2`. And it's here where :py:class:`.CopyResult` comes into play. We can see that the copy process failed because the counterpart with `external_id` 2 was not found in the destination project. -By observing the :py:attr:`.django_copyist.config.CopyResult.set_to_filter_map` attribute, we can see that the counterpart with `external_id` 2 was not +By observing the :py:attr:`.CopyResult.set_to_filter_map` attribute, we can see that the counterpart with `external_id` 2 was not matched. If it is happening in interactive context, you can prompt user to resolve this issue or accept the fact that some data won't be copied. -If we want to confirm that the copy process should continue regardless, we can set the :py:attr:`.confirm_write` attribute to `True` and pass the :py:attr:`.django_copyist.config.CopyResult.set_to_filter_map` attribute to the :py:attr:`.CopyRequest` object. +If we want to confirm that the copy process should continue regardless, we can set the :py:attr:`.confirm_write` attribute to `True` and pass the :py:attr:`.django_copyist.config.CopyResult.set_to_filter_map` attribute to the :py:class:`~.CopyRequest` object. .. note:: - The :py:attr:`django_copyist.config.CopyRequest.set_to_filter_map` is passed, so that :py:class:`.Copyist` can verify that list of unmatched - values remained the same between copy calls. If it changed, unsuccessful result with reason :py:attr:`.django_copyist.config.AbortReason.DATA_CHANGED_STF` will be returned. + The :py:attr:`.CopyRequest.set_to_filter_map` is passed, so that :py:class:`~django_copyist.copyist.Copyist` can verify that list of unmatched + values remained the same between copy calls. If it changed, unsuccessful result with reason :py:attr:`~.DATA_CHANGED_STF` will be returned. Set to filter using custom function ------------------------------------ @@ -600,7 +600,7 @@ You can read more on signature at :protocol:`~.django_copyist.config.SetToFilter Ignoring models during copy with SET_TO_FILTER ------------------------------------------------ -You probably noticed the :py:attr:`.django_copyist.copy_request.CopyResult.ignored_map` attribute in the previous examples. +You probably noticed the :py:attr:`.CopyResult.ignored_map` attribute in the previous examples. So how exactly it is used? For example, lets assume you want to have same config as in `SET_TO_FILTER` example, but you want to ignore `Task` model if it can't match all counterparts: @@ -1240,7 +1240,7 @@ Also :py:class:`~.PostcopyStep` supports `filter_field_to_input_key` with :py:at Note on ordering of operations ------------------------------ -Now that we are familiar with different steps that :py:class:`~.Copyist` can go through in process, +Now that we are familiar with different steps that :py:class:`~django_copyist.copyist.Copyist` can go through in process, let's talk about the order of operations. The order of operations is as follows: diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 4a16822..71a5723 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -68,7 +68,7 @@ Assuming you have following models in your Django app: And you want to create full copy of company with all nested data, but also want it to be created with different name and address. -In this case you should write following ModelCopyConfig +In this case you should write following :py:class:`~.django_copyist.config.ModelCopyConfig` .. code-block:: python @@ -162,7 +162,7 @@ And then you can execute copy action like this: With this, all company data should be copied. That seems like a lot to take in, so let's break it down to what exactly happens here: -1. We define a `ModelCopyConfig` for the `Company` model. +1. We define a :py:class:`~.django_copyist.config.ModelCopyConfig` for the `Company` model. .. code-block:: python @@ -171,9 +171,9 @@ That seems like a lot to take in, so let's break it down to what exactly happens filter_field_to_input_key={"id": "company_id"}, ... -`ModelCopyConfig` is a class that defines how to copy a model. It takes the model class as the first argument and a dictionary that maps the filter field to the input key. This is used to find the object to copy. +:py:class:`~.django_copyist.config.ModelCopyConfig` is a class that defines how to copy a model. It takes the model class as the first argument and a dictionary that maps the filter field to the input key. This is used to find the object to copy. -2. Next we define `field_copy_actions` for the `Company` model. +2. Next we define :py:attr:`.ModelCopyConfig.field_copy_actions` for the `Company` model. .. code-block:: python @@ -213,19 +213,19 @@ That seems like a lot to take in, so let's break it down to what exactly happens ), ... -`field_copy_actions` is a dictionary that maps the field name to a `FieldCopyConfig` object. +:py:attr:`.ModelCopyConfig.field_copy_actions` is a dictionary that maps the field name to a :py:class:`~.FieldCopyConfig` object. -The `FieldCopyConfig` object defines how to copy the field. In this case, we take the `name` and `address` fields from the input data. +The :py:class:`~.FieldCopyConfig` object defines how to copy the field. In this case, we take the `name` and `address` fields from the input data. -`TAKE_FROM_ORIGIN` is a shortcut for creating `FieldCopyConfig` with `CopyActions.TAKE_FROM_ORIGIN` action, which takes value for new object from original object. +:py:attr:`~django_copyist.config.TAKE_FROM_ORIGIN` is a shortcut for creating :py:class:`~.FieldCopyConfig` with :py:attr:`~.CopyActions.TAKE_FROM_ORIGIN` action, which takes value for new object from original object. We also define how to copy the `projects` and `employees` fields. -We use the `MakeCopy` action to copy the related objects. -`MakeCopy` is a shortcut for creating `FieldCopyConfig` with `CopyActions.MAKE_COPY` action and reference to given model. -Nested `MakeCopy` automatically propagate parent id to child object. +We use the :py:attr:`~.MakeCopy` action to copy the related objects. +:py:attr:`~.MakeCopy` is a shortcut for creating :py:class:`~.FieldCopyConfig` with :py:attr:`CopyActions.MAKE_COPY` action and reference to given model. +Nested :py:attr:`~.MakeCopy` automatically propagate parent id to child object. -3. We define `compound_copy_actions` for the `Company` model. +3. We define :py:attr:`~.ModelCopyConfig.compound_copy_actions` for the `Company` model. .. code-block:: python @@ -242,17 +242,17 @@ Nested `MakeCopy` automatically propagate parent id to child object. ) ... -`compound_copy_actions` is a list of `ModelCopyConfig` objects that define how +:py:attr:`~.ModelCopyConfig.compound_copy_actions` is a list of :py:class:`~.ModelCopyConfig` objects that define how to copy related objects that are not directly related to the model, or related through multiple relations that need to be created beforehand. -`compound_copy_actions` are executed after all fields are copied. +:py:attr:`~.ModelCopyConfig.compound_copy_actions` are executed after all fields are copied. In this case, we define how to copy the `Task` model. We take the `name` and `description` fields from the original object. We also define how to copy the `counterparts`, `project`, and `assignee` fields. -`UpdateToCopied` is a shortcut for creating `FieldCopyConfig` with `CopyActions.UPDATE_TO_COPIED` action and reference to given model. +:py:func:`~.UpdateToCopied` is a shortcut for creating :py:class:`~.FieldCopyConfig` with :py:attr:`CopyActions.UPDATE_TO_COPIED` action and reference to given model. It will search mapping of previously copied objects and update reference to copied object. -4. We create a `CopyRequest` object with the `CopyistConfig` and input data. +4. We create a :py:class:`~.CopyRequest` object with the :py:class:`~.CopyistConfig` and input data. .. code-block:: python @@ -267,24 +267,25 @@ It will search mapping of previously copied objects and update reference to copi ) ... -`CopyRequest` is a class that defines the copy request. It takes the `CopyistConfig` object, input data, and a boolean flag that indicates whether to confirm the write operation. +:py:class:`~.CopyRequest` is a class that defines the copy request. It takes the `CopyistConfig` object, input data, and a boolean flag that indicates whether to confirm the write operation. -`CopyistConfig` is a class that defines the configuration for the copy operation. It takes a list of `ModelCopyConfig` objects. +:py:class:`~.CopyistConfig` is a class that defines the configuration for the copy operation. It takes a list of :py:class:`~.ModelCopyConfig` objects. -`input_data` is a dictionary that contains the input data for the copy operation. It is later used in filtering or `TAKE_FROM_INPUT` actions. +:py:attr:`.CopyResult.input_data` is a dictionary that contains the input data for the copy operation. It is later used in filtering or :py:attr:`~.TAKE_FROM_INPUT` actions. -`confirm_write` is a boolean flag that indicates whether to confirm the write operation, +:py:attr:`.CopyResult.confirm_write` is a boolean flag that indicates whether to confirm the write operation, even if there are issues with matching objects in origin location with objects in target destination. It is not used in this example, but you can read more about it in overview section of this documentation. 5. We execute the copy request. + .. code-block:: python result = Copyist(copy_request).execute_copy_request() -`Copyist` is a class that executes the copy request. It takes the `CopyRequest` object as an argument. +:py:class:`~django_copyist.copyist.Copyist` is a class that executes the copy request. It takes the :py:class:`~.CopyRequest` object as an argument. -`execute_copy_request` method returns `CopyResult` object that contains information about the copy operation. Read more about it in overview section. +:py:attr:`.CopyResult.execute_copy_request` method returns :py:class:`~.CopyResult` object that contains information about the copy operation. Read more about it in overview section. And like this you have copied the company with all related data and can see and edit configuration in one place. diff --git a/pyproject.toml b/pyproject.toml index ad3d772..ced7049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "django-copyist" -version = "0.1.2" +version = "0.1.3" description = "Tool for precise and efficient django model copying" authors = ["abondar"] license = "Apache-2.0"