diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index bd3c364e95..cedab70234 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -163,9 +163,12 @@ commands: - run: name: Install GDAL, Fiona and pytables command: conda activate kedro_builder; conda install gdal fiona pytables -c conda-forge -y + - run: + name: Show pip information + command: conda activate kedro_builder; pip debug --verbose - run: name: Install all requirements - command: conda activate kedro_builder; pip install -r test_requirements.txt -U + command: conda activate kedro_builder; pip install -v -r test_requirements.txt -U - run: name: Print Python environment command: conda activate kedro_builder; make print-python-env @@ -245,14 +248,14 @@ jobs: steps: - run: name: Run unit tests in parallel - command: make test + command: PYTEST_ADDOPTS="-v" make test - when: condition: equal: [ "3.10", <> ] steps: - run: name: Run unit tests sequentially - command: pytest tests --cov-config pyproject.toml + command: pytest -v tests --cov-config pyproject.toml win_unit_tests: diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 0000000000..e03635f196 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,13 @@ +FROM gitpod/workspace-full:2023-05-08-21-16-55 + +# Some datasets work on 3.8 only +RUN pyenv install 3.8.15\ + && pyenv global 3.8.15 + +# VideoDataSet +RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends libgl1 +RUN sudo apt-get install make +RUN npm install -g @mermaid-js/mermaid-cli +# https://stackoverflow.com/questions/69564238/puppeteer-error-failed-to-launch-the-browser-process +# https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-doesnt-launch-on-linux +RUN sudo apt-get install -y --no-install-recommends libatk-bridge2.0-0 libcups2 ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils diff --git a/.gitpod.yml b/.gitpod.yml index b8fedd3f93..c2ec591de9 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,32 +1,18 @@ -image: gitpod/workspace-full:legacy-dazzle-v1 - +image: + file: .gitpod.Dockerfile tasks: - # We want packages installed during the pre-build init steps to go to /workspace - # rather than ~ so that they are persisted. Gitpod sets PIP_USER=yes to ensure this, - # but pre-commit requires PIP_USER=no. Hence we set PIP_USER=no and use - # pip install --user to install to /workspace. - name: kedro - before: | - echo PIP_USER=no >> ~/.bashrc && export PIP_USER=no + init: | make sign-off - pip install -r test_requirements.txt --user - gp sync-done kedro - command: | - pre-commit install --install-hooks - clear - # Generate a test Kedro project at /workspace/project using the local Kedro version. - - name: test-project - init: | - gp sync-await kedro - export PIP_USER=no + pip install -e /workspace/kedro cd /workspace - pip install -e /workspace/kedro --no-deps - source ~/.bashrc yes project | kedro new -s pandas-iris --checkout main + pip install -r /workspace/kedro/test_requirements.txt + cd /workspace/kedro + pre-commit install --install-hooks + command: | - pip install -e /workspace/kedro --no-deps - cd /workspace/project clear kedro info diff --git a/.readthedocs.yml b/.readthedocs.yml index 4b1c5f4824..2df6853225 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -16,6 +16,7 @@ build: post_create_environment: - npm install -g @mermaid-js/mermaid-cli pre_build: + - pip freeze - python -m sphinx -WETan -j auto -D language=en -b linkcheck -d _build/doctrees docs/source _build/linkcheck # Build documentation in the docs/ directory with Sphinx diff --git a/CITATION.cff b/CITATION.cff index 191a56a8f7..256c577eb8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -45,6 +45,6 @@ authors: - family-names: Theisen given-names: Merel title: Kedro -version: 0.18.10 -date-released: 2023-06-08 +version: 0.18.11 +date-released: 2023-07-03 url: https://github.com/kedro-org/kedro diff --git a/CODEOWNERS b/CODEOWNERS index 08a8744c60..a4c421df40 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ -* @idanov +* @merelcht docs/ @yetudada @astrojuanlu @stichbury diff --git a/RELEASE.md b/RELEASE.md index c8ffa1188a..4d645c1279 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,26 +3,60 @@ ## Major features and improvements ## Bug fixes and other changes +* Compare for protocol and delimiter in `PartitionedDataSet` to be able to pass the protocol to partitions which paths starts with the same characters as the protocol (e.g. `s3://s3-my-bucket`). ## Breaking changes to the API ## Migration guide from Kedro 0.18.* to 0.19.* -# Upcoming Release 0.18.11 +# Upcoming Release 0.18.12 ## Major features and improvements ## Bug fixes and other changes -* Reworked micropackaging workflow to use standard Python packaging practices. -* Make `kedro micropkg package` accept `--verbose`. ## Documentation changes -* Added documentation for developing a Kedro project using a Databricks workspace. ## Breaking changes to the API ## Upcoming deprecations for Kedro 0.19.0 +# Release 0.18.11 + +## Major features and improvements +* Added `databricks-iris` as an official starter. + +## Bug fixes and other changes +* Reworked micropackaging workflow to use standard Python packaging practices. +* Make `kedro micropkg package` accept `--verbose`. + +## Documentation changes +* Significant improvements to the documentation that covers working with Databricks and Kedro, including a new page for workspace-only development, and a guide to choosing the best workflow for your use case. +* Updated documentation for deploying with Prefect for version 2.0. + +## Upcoming deprecations for Kedro 0.19.0 +* Renamed dataset and error classes, in accordance with the [Kedro lexicon](https://github.com/kedro-org/kedro/wiki/Kedro-documentation-style-guide#kedro-lexicon). Dataset classes ending with "DataSet" and error classes starting with "DataSet" are deprecated and will be removed in 0.19.0. Note that all of the below classes are also importable from `kedro.io`; only the module where they are defined is listed as the location. + +| Type | Deprecated Alias | Location | +| --------------------------- | --------------------------- | ------------------------------ | +| `CachedDataset` | `CachedDataSet` | `kedro.io.cached_dataset` | +| `LambdaDataset` | `LambdaDataSet` | `kedro.io.lambda_dataset` | +| `IncrementalDataset` | `IncrementalDataSet` | `kedro.io.partitioned_dataset` | +| `MemoryDataset` | `MemoryDataSet` | `kedro.io.memory_dataset` | +| `PartitionedDataset` | `PartitionedDataSet` | `kedro.io.partitioned_dataset` | +| `DatasetError` | `DataSetError` | `kedro.io.core` | +| `DatasetAlreadyExistsError` | `DataSetAlreadyExistsError` | `kedro.io.core` | +| `DatasetNotFoundError` | `DataSetNotFoundError` | `kedro.io.core` | + +## Community contributions +Many thanks to the following Kedroids for contributing PRs to this release: + +* [jmalovera10](https://github.com/jmalovera10) +* [debugger24](https://github.com/debugger24) +* [juliushetzel](https://github.com/juliushetzel) +* [jacobweiss2305](https://github.com/jacobweiss2305) +* [eduardoconto](https://github.com/eduardoconto) + # Release 0.18.10 ## Major features and improvements diff --git a/docs/source/conf.py b/docs/source/conf.py index 4140dc4381..5624e36ed7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -551,6 +551,7 @@ def setup(app): # https://github.com/kedro-org/kedro/issues/1772 mermaid_output_format = "png" # https://github.com/mermaidjs/mermaid.cli#linux-sandbox-issue +# https://github.com/mermaid-js/mermaid-cli/issues/544 mermaid_params = ["-p", here / "puppeteer-config.json", "-s", "2"] # https://github.com/kedro-org/kedro/issues/2451 mermaid_version = mermaid_init_js = "" diff --git a/docs/source/deployment/prefect.md b/docs/source/deployment/prefect.md index 556097faa6..64d1018984 100644 --- a/docs/source/deployment/prefect.md +++ b/docs/source/deployment/prefect.md @@ -1,40 +1,70 @@ # Prefect -This page explains how to run your Kedro pipeline using [Prefect Core](https://www.prefect.io/products/core/), an open-source workflow management system. +This page explains how to run your Kedro pipeline using [Prefect 2.0](https://www.prefect.io/products/core/), an open-source workflow management system. -In scope of this deployment, we are interested in [Prefect Server](https://docs.prefect.io/orchestration/server/overview.html#what-is-prefect-server), an open-source backend that makes it easy to monitor and execute your Prefect flows and automatically extends the Prefect Core. Prefect Server ships out-of-the-box with a fully featured user interface. +The scope of this documentation is the deployment to a self hosted [Prefect Server](https://docs.prefect.io/2.10.17/host/), which is an open-source backend that makes it easy to monitor and execute your Prefect flows and automatically extends Prefect 2.0. We will use an [Agent that dequeues submitted flow runs from a Work Queue](https://docs.prefect.io/2.10.17/tutorial/deployments/#why-workpools-and-workers). ```{note} -This deployment has been tested using kedro 0.17.6, 0.17.7 and 0.18.2 with prefect version 1.1.0. - -The current implementation has not been tested with prefect 2.0.0. +This deployment has been tested using Kedro 0.18.10 with Prefect version 2.10.17. If you want to deploy with Prefect 1.0, we recommend you review [earlier versions of Kedro's Prefect deployment documentation](https://docs.kedro.org/en/0.18.9/deployment/prefect.html). ``` ## Prerequisites -To use Prefect Core and Prefect Server, ensure you have the following prerequisites in place: +To use Prefect 2.0 and Prefect Server, ensure you have the following prerequisites in place: + +- [Prefect 2.0 is installed](https://docs.prefect.io/2.10.17/getting-started/installation/#installing-the-latest-version) on your machine + +## Setup + +Configure your `PREFECT_API_URL` to point to your local Prefect instance: + +```bash +prefect config set PREFECT_API_URL="http://127.0.0.1:4200/api" +``` + +For each new Kedro project you create, you need to decide whether to opt into [usage analytics](https://github.com/kedro-org/kedro-plugins/tree/main/kedro-telemetry). Your decision is recorded in the `.telemetry` file stored in the project root. + +```{important} +When you run a Kedro project locally, you are asked on the first `kedro` command for the project, but in this use case, the project will hang unless you follow these instructions. +``` + +Create a `.telemetry` file manually and put it in the **root of your Kedro project** and add your preference to give or decline consent. To do this, specify either `true` (to give consent) or `false`. The example given below accepts Kedro's usage analytics. + +```text +consent: true +``` -- [Prefect Core is installed](https://docs.prefect.io/core/getting_started/install.html) on your machine -- [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) are installed and Docker Engine is running -- [Prefect Server is up and running](https://docs.prefect.io/orchestration/Server/deploy-local.html) -- `PREFECT__LOGGING__EXTRA_LOGGERS` environment variable is set (this is required to get Kedro logs published): +Run a Prefect Server instance: -```console -export PREFECT__LOGGING__EXTRA_LOGGERS="['kedro']" +```bash +prefect server start ``` -## How to run your Kedro pipeline using Prefect +In a separate terminal, [create a work pool](https://docs.prefect.io/2.10.17/concepts/work-pools/#work-pool-configuration) to organize the work and [create a work queue](https://docs.prefect.io/2.10.17/concepts/work-pools/#work-queues) for your agent to pull from: -### Convert your Kedro pipeline to Prefect flow +```bash +prefect work-pool create --type prefect-agent +prefect work-queue create --pool +``` + +Now run a Prefect Agent that subscribes to a work queue inside the work pool you created: + +```bash +prefect agent start --pool --work-queue +``` -To build a [Prefect flow](https://docs.prefect.io/core/concepts/flows.html) for your Kedro pipeline programmatically and register it with the Prefect API, use the following Python script, which should be stored in your project’s root directory: +## How to run your Kedro pipeline using Prefect 2.0 + +### Convert your Kedro pipeline to Prefect 2.0 flow + +To build a [Prefect flow](https://docs.prefect.io/core/concepts/flows.html) for your Kedro pipeline programmatically and register it with the Prefect API, use the following Python script, which should be stored in your project’s **root directory**: ```python # /register_prefect_flow.py +import click from pathlib import Path -from typing import Any, Dict, List, Tuple, Union +from typing import Dict, List, Union, Callable -import click from kedro.framework.hooks.manager import _create_hook_manager from kedro.framework.project import pipelines from kedro.framework.session import KedroSession @@ -42,204 +72,194 @@ from kedro.framework.startup import bootstrap_project from kedro.io import DataCatalog, MemoryDataSet from kedro.pipeline.node import Node from kedro.runner import run_node -from prefect import Client, Flow, Task -from prefect.exceptions import ClientError + +from prefect import flow, task, get_run_logger +from prefect.deployments import Deployment @click.command() -@click.option("-p", "--pipeline", "pipeline_name", default=None) -@click.option("--env", "-e", type=str, default=None) -@click.option("--package_name", "package_name", default="kedro_prefect") -def prefect_deploy(pipeline_name, env, package_name): +@click.option("-p", "--pipeline", "pipeline_name", default="__default__") +@click.option("--env", "-e", type=str, default="base") +@click.option("--deployment_name", "deployment_name", default="example") +@click.option("--work_pool_name", "work_pool_name", default="default") +@click.option("--work_queue_name", "work_queue_name", default="default") +@click.option("--version", "version", default="1.0") +def prefect_deploy( + pipeline_name, env, deployment_name, work_pool_name, work_queue_name, version +): """Register a Kedro pipeline as a Prefect flow.""" - # Project path and metadata required for session initialization task. + # Pipeline name to execute + pipeline_name = pipeline_name or "__default__" + + # Use standard deployment configuration for local execution. If you require a different + # infrastructure, check the API docs for Deployments at: https://docs.prefect.io/latest/api-ref/prefect/deployments/ + deployment = Deployment.build_from_flow( + flow=my_flow, + name=deployment_name, + path=str(Path.cwd()), + version=version, + parameters={ + "pipeline_name": pipeline_name, + "env": env, + }, + infra_overrides={"env": {"PREFECT_LOGGING_LEVEL": "DEBUG"}}, + work_pool_name=work_pool_name, + work_queue_name=work_queue_name, + ) + + deployment.apply() + + +@flow(name="my_flow") +def my_flow(pipeline_name: str, env: str): + logger = get_run_logger() project_path = Path.cwd() + metadata = bootstrap_project(project_path) + logger.info("Project name: %s", metadata.project_name) - pipeline_name = pipeline_name or "__default__" - pipeline = pipelines.get(pipeline_name) + logger.info("Initializing Kedro...") + execution_config = kedro_init( + pipeline_name=pipeline_name, project_path=project_path, env=env + ) + + logger.info("Building execution layers...") + execution_layers = init_kedro_tasks_by_execution_layer( + pipeline_name, execution_config + ) + + for layer in execution_layers: + logger.info("Running layer...") + for node_task in layer: + logger.info("Running node...") + node_task() - tasks = {} - for node, parent_nodes in pipeline.node_dependencies.items(): - # Use a function for task instantiation which avoids duplication of - # tasks - _, tasks = instantiate_task(node, tasks) - parent_tasks = [] - for parent in parent_nodes: - parent_task, tasks = instantiate_task(parent, tasks) - parent_tasks.append(parent_task) +@task() +def kedro_init( + pipeline_name: str, + project_path: Path, + env: str, +): + """ + Initializes a Kedro session and returns the DataCatalog and + KedroSession + """ + # bootstrap project within task / flow scope - tasks[node._unique_key]["parent_tasks"] = parent_tasks + logger = get_run_logger() + logger.info("Bootstrapping project") + bootstrap_project(project_path) - # Below task is used to instantiate a KedroSession within the scope of a - # Prefect flow - init_task = KedroInitTask( - pipeline_name=pipeline_name, + session = KedroSession.create( project_path=project_path, - package_name=package_name, env=env, ) + # Note that for logging inside a Prefect task logger is used. + logger.info("Session created with ID %s", session.session_id) + pipeline = pipelines.get(pipeline_name) + logger.info("Loading context...") + context = session.load_context() + catalog = context.catalog + logger.info("Registering datasets...") + unregistered_ds = pipeline.data_sets() - set(catalog.list()) # NOQA + for ds_name in unregistered_ds: + catalog.add(ds_name, MemoryDataSet()) + return {"catalog": catalog, "sess_id": session.session_id} + + +def init_kedro_tasks_by_execution_layer( + pipeline_name: str, + execution_config: Union[None, Dict[str, Union[DataCatalog, str]]] = None, +) -> List[List[Callable]]: + """ + Inits the Kedro tasks ordered topologically in groups, which implies that an earlier group + is the dependency of later one. - with Flow(pipeline_name) as flow: - generate_flow(init_task, tasks) - instantiate_client(metadata.project_name) - - # Register the flow with the server - flow.register(project_name=metadata.project_name) - - # Start a local agent that can communicate between the server - # and your flow code - flow.run_agent() - - -class KedroInitTask(Task): - """Task to initialize KedroSession""" - - def __init__( - self, - pipeline_name: str, - package_name: str, - project_path: Union[Path, str] = None, - env: str = None, - extra_params: Dict[str, Any] = None, - *args, - **kwargs, - ): - self.project_path = Path(project_path or Path.cwd()).resolve() - self.extra_params = extra_params - self.pipeline_name = pipeline_name - self.env = env - super().__init__(name=f"{package_name}_init", *args, **kwargs) - - def run(self) -> Dict[str, Union[DataCatalog, str]]: - """ - Initializes a Kedro session and returns the DataCatalog and - KedroSession - """ - # bootstrap project within task / flow scope - bootstrap_project(self.project_path) - - session = KedroSession.create( - project_path=self.project_path, - env=self.env, - extra_params=self.extra_params, # noqa: E501 - ) - # Note that for logging inside a Prefect task self.logger is used. - self.logger.info("Session created with ID %s", session.session_id) - pipeline = pipelines.get(self.pipeline_name) - context = session.load_context() - catalog = context.catalog - unregistered_ds = pipeline.data_sets() - set(catalog.list()) # NOQA - for ds_name in unregistered_ds: - catalog.add(ds_name, MemoryDataSet()) - return {"catalog": catalog, "sess_id": session.session_id} - - -class KedroTask(Task): - """Kedro node as a Prefect task.""" - - def __init__(self, node: Node): - self._node = node - super().__init__(name=node.name, tags=node.tags) - - def run(self, task_dict: Dict[str, Union[DataCatalog, str]]): - run_node( - self._node, - task_dict["catalog"], - _create_hook_manager(), - task_dict["sess_id"], - ) - + Args: + pipeline_name (str): The pipeline name to execute + execution_config (Union[None, Dict[str, Union[DataCatalog, str]]], optional): + The required execution config for each node. Defaults to None. -def instantiate_task( - node: Node, - tasks: Dict[str, Dict[str, Union[KedroTask, List[KedroTask]]]], -) -> Tuple[KedroTask, Dict[str, Dict[str, Union[KedroTask, List[KedroTask]]]]]: + Returns: + List[List[Callable]]: A list of topologically ordered task groups """ - Function pulls node task from dictionary. If node task not - available in the function instantiates the tasks and adds - it to . In this way we avoid duplicate instantiations of - the same node task. - Args: - node: Kedro node for which a Prefect task is being created. - tasks: dictionary mapping node names to a dictionary containing - node tasks and parent node tasks. + pipeline = pipelines.get(pipeline_name) - Returns: Prefect task for the passed node and task dictionary. + execution_layers = [] - """ - if tasks.get(node._unique_key) is not None: - node_task = tasks[node._unique_key]["task"] - else: - node_task = KedroTask(node) - tasks[node._unique_key] = {"task": node_task} + # Return a list of the pipeline nodes in topologically ordered groups, + # i.e. if node A needs to be run before node B, it will appear in an + # earlier group. + for layer in pipeline.grouped_nodes: + execution_layer = [] + for node in layer: + # Use a function for task instantiation which avoids duplication of + # tasks + task = instantiate_task(node, execution_config) + execution_layer.append(task) + execution_layers.append(execution_layer) - # return tasks as it is mutated. We want to make this obvious to the user. - return node_task, tasks # type: ignore[return-value] + return execution_layers -def generate_flow( - init_task: KedroInitTask, - tasks: Dict[str, Dict[str, Union[KedroTask, List[KedroTask]]]], +def kedro_task( + node: Node, task_dict: Union[None, Dict[str, Union[DataCatalog, str]]] = None ): + run_node( + node, + task_dict["catalog"], + _create_hook_manager(), + task_dict["sess_id"], + ) + + +def instantiate_task( + node: Node, + execution_config: Union[None, Dict[str, Union[DataCatalog, str]]] = None, +) -> Callable: """ - Constructs a Prefect flow given a task dictionary. Task dictionary - maps Kedro node names to a dictionary containing a node task and its - parents. + Function that wraps a Node inside a task for future execution Args: - init_task: Prefect initialisation tasks. Used to instantiate a Kedro - session within the scope of a Prefect flow. - tasks: dictionary mapping Kedro node names to a dictionary - containing a corresponding node task and its parents. + node: Kedro node for which a Prefect task is being created. + execution_config: The configurations required for the node to execute + that includes catalogs and session id + + Returns: Prefect task for the passed node - Returns: None """ - child_task_dict = init_task - for task in tasks.values(): - node_task = task["task"] - if len(task["parent_tasks"]) == 0: - # When a task has no parent only the session init task should - # precede it. - parent_tasks = [init_task] - else: - parent_tasks = task["parent_tasks"] - # Set upstream tasks and bind required kwargs. - # Note: Unpacking the return from init tasks will generate two - # sub-tasks in the prefect graph. To avoid this we pass the init - # return on unpacked. - node_task.bind(upstream_tasks=parent_tasks, task_dict=child_task_dict) - - -def instantiate_client(project_name: str): - """Initiates Prefect client""" - client = Client() - try: - client.create_project(project_name=project_name) - except ClientError: - raise + return task(lambda: kedro_task(node, execution_config)).with_options(name=node.name) if __name__ == "__main__": prefect_deploy() ``` -```{note} -The script launches a [local agent](https://docs.prefect.io/orchestration/agents/local.html). Remember to stop the agent with Ctrl-C when you complete. +Then, run the deployment script in other terminal: + +```bash +python register_prefect_flow.py --work_pool_name --work_queue_name ``` +```{note} +Be sure that your Prefect Server is up and running. Verify that the deployment script arguments match the work pool and work queue names. +``` ### Run Prefect flow -Now, having the flow registered, you can use [Prefect UI](https://docs.prefect.io/orchestration/ui/dashboard.html) to orchestrate and monitor it. +Now, having the flow registered, you can use [Prefect Server UI](https://docs.prefect.io/2.10.17/host/) to orchestrate and monitor it. -Navigate to http://localhost:8080/default?flows= to see your registered flow. +Navigate to http://localhost:4200/deployments to see your registered flow. -![](../meta/images/prefect_flows.png) +![prefect_2_flow_deployment](../meta/images/prefect_2_flow_deployment.png) -Click on the flow to open it and then trigger your flow using the "RUN"/"QUICK RUN" button. +Click on the flow to open it and then trigger your flow using the "RUN" > "QUICK RUN" button and leave the parameters by default. If you want to run a specific pipeline you can replace the `__default__` value. + +```{note} +Be sure that both your Prefect Server and Agent are up and running. +``` -![](../meta/images/prefect_flow_details.png) +![prefect_2_flow_details](../meta/images/prefect_2_flow_details.png) diff --git a/docs/source/development/commands_reference.md b/docs/source/development/commands_reference.md index 31b92d9a92..097984702e 100644 --- a/docs/source/development/commands_reference.md +++ b/docs/source/development/commands_reference.md @@ -114,7 +114,7 @@ Returns output similar to the following, depending on the version of Kedro used | |/ / _ \/ _` | '__/ _ \ | < __/ (_| | | | (_) | |_|\_\___|\__,_|_| \___/ -v0.18.10 +v0.18.11 Kedro is a Python framework for creating reproducible, maintainable diff --git a/docs/source/extend_kedro/plugins.md b/docs/source/extend_kedro/plugins.md index 37dbd5abe9..51cb3b1946 100644 --- a/docs/source/extend_kedro/plugins.md +++ b/docs/source/extend_kedro/plugins.md @@ -84,7 +84,7 @@ setup( After that you can use this starter with `kedro new --starter=test_plugin_starter`. ```{note} -If your starter lives on a git repository, by default Kedro attempts to use a tag or branch labelled with your version of Kedro, e.g. `0.18.10`. This means that you can host different versions of your starter template on the same repository, and the correct one will automatically be used. If you do not wish to follow this structure, you should override it with the `checkout` flag, e.g. `kedro new --starter=test_plugin_starter --checkout=main`. +If your starter lives on a git repository, by default Kedro attempts to use a tag or branch labelled with your version of Kedro, e.g. `0.18.11`. This means that you can host different versions of your starter template on the same repository, and the correct one will automatically be used. If you do not wish to follow this structure, you should override it with the `checkout` flag, e.g. `kedro new --starter=test_plugin_starter --checkout=main`. ``` ## Working with `click` diff --git a/docs/source/kedro_datasets.rst b/docs/source/kedro_datasets.rst index b8e9f5d442..b3d3ab328b 100644 --- a/docs/source/kedro_datasets.rst +++ b/docs/source/kedro_datasets.rst @@ -14,6 +14,7 @@ kedro_datasets kedro_datasets.api.APIDataSet kedro_datasets.biosequence.BioSequenceDataSet kedro_datasets.dask.ParquetDataSet + kedro_datasets.databricks.ManagedTableDataSet kedro_datasets.email.EmailMessageDataSet kedro_datasets.geopandas.GeoJSONDataSet kedro_datasets.holoviews.HoloviewsWriter @@ -45,6 +46,7 @@ kedro_datasets kedro_datasets.spark.SparkDataSet kedro_datasets.spark.SparkHiveDataSet kedro_datasets.spark.SparkJDBCDataSet + kedro_datasets.spark.SparkStreamingDataSet kedro_datasets.svmlight.SVMLightDataSet kedro_datasets.tensorflow.TensorFlowModelDataSet kedro_datasets.text.TextDataSet diff --git a/docs/source/meta/images/prefect_2_flow_deployment.png b/docs/source/meta/images/prefect_2_flow_deployment.png new file mode 100644 index 0000000000..b3f725447f Binary files /dev/null and b/docs/source/meta/images/prefect_2_flow_deployment.png differ diff --git a/docs/source/meta/images/prefect_2_flow_details.png b/docs/source/meta/images/prefect_2_flow_details.png new file mode 100644 index 0000000000..c935e974a8 Binary files /dev/null and b/docs/source/meta/images/prefect_2_flow_details.png differ diff --git a/docs/source/puppeteer-config.json b/docs/source/puppeteer-config.json index 3201af7b73..5adc866e94 100644 --- a/docs/source/puppeteer-config.json +++ b/docs/source/puppeteer-config.json @@ -1,3 +1,4 @@ { - "args": ["--no-sandbox"] + "args": ["--no-sandbox"], + "headless": "old" } diff --git a/features/jupyter.feature b/features/jupyter.feature index 188d07f2f4..65b5173442 100644 --- a/features/jupyter.feature +++ b/features/jupyter.feature @@ -22,6 +22,6 @@ Feature: Jupyter targets in new project Given I have added a test jupyter notebook When I execute the test jupyter notebook and save changes And I execute the kedro jupyter command "convert --all" - And Wait until the process is finished + And Wait until the process is finished for up to "120" seconds Then I should get a successful exit code And Code cell with node tag should be converted into kedro node diff --git a/features/steps/cli_steps.py b/features/steps/cli_steps.py index 58e5c213af..c0a9453409 100644 --- a/features/steps/cli_steps.py +++ b/features/steps/cli_steps.py @@ -352,10 +352,10 @@ def wait_for_notebook_to_run(context, timeout): raise TimeoutError("Failed to run Jupyter server in time") -@when("Wait until the process is finished") -def wait(context): +@when('Wait until the process is finished for up to "{timeout:d}" seconds') +def wait(context, timeout): """Wait for child process to terminate.""" - context.result.wait() + context.result.wait(timeout) @when("I execute the test jupyter notebook and save changes") diff --git a/features/windows_reqs.txt b/features/windows_reqs.txt index 191c9e7293..c41bf77a4d 100644 --- a/features/windows_reqs.txt +++ b/features/windows_reqs.txt @@ -3,7 +3,7 @@ # everything, so just this subset will be enough for CI behave==1.2.6 pandas~=1.3 -psutil==5.8.0 +psutil~=5.8 requests~=2.20 toml~=0.10.1 PyYAML>=4.2, <7.0 diff --git a/kedro/__init__.py b/kedro/__init__.py index ac4b0c5332..be9febd329 100644 --- a/kedro/__init__.py +++ b/kedro/__init__.py @@ -3,4 +3,4 @@ configuration and pipeline assembly. """ -__version__ = "0.18.10" +__version__ = "0.18.11" diff --git a/kedro/framework/cli/project.py b/kedro/framework/cli/project.py index d7c3421601..7914a4c9ee 100644 --- a/kedro/framework/cli/project.py +++ b/kedro/framework/cli/project.py @@ -44,7 +44,7 @@ TO_NODES_HELP = """A list of node names which should be used as an end point.""" NODE_ARG_HELP = """Run only nodes with specified names.""" RUNNER_ARG_HELP = """Specify a runner that you want to run the pipeline with. -Available runners: 'SequentialRunner', 'ParallelRunner' and 'ThreadRun'.""" +Available runners: 'SequentialRunner', 'ParallelRunner' and 'ThreadRunner'.""" ASYNC_ARG_HELP = """Load and save node inputs and outputs asynchronously with threads. If not specified, load and save datasets synchronously.""" TAG_ARG_HELP = """Construct the pipeline using only nodes which have this tag diff --git a/kedro/framework/cli/starters.py b/kedro/framework/cli/starters.py index 97fb9e5727..db3a24a321 100644 --- a/kedro/framework/cli/starters.py +++ b/kedro/framework/cli/starters.py @@ -69,6 +69,7 @@ class KedroStarterSpec: # pylint: disable=too-few-public-methods KedroStarterSpec("pyspark", _STARTERS_REPO, "pyspark"), KedroStarterSpec("pyspark-iris", _STARTERS_REPO, "pyspark-iris"), KedroStarterSpec("spaceflights", _STARTERS_REPO, "spaceflights"), + KedroStarterSpec("databricks-iris", _STARTERS_REPO, "databricks-iris"), ] # Set the origin for official starters for starter_spec in _OFFICIAL_STARTER_SPECS: diff --git a/kedro/io/__init__.py b/kedro/io/__init__.py index 5899f904c6..351c7f510e 100644 --- a/kedro/io/__init__.py +++ b/kedro/io/__init__.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from .cached_dataset import CachedDataSet, CachedDataset +from .cached_dataset import CachedDataset, CachedDataSet from .core import ( AbstractDataSet, AbstractVersionedDataSet, @@ -13,13 +13,13 @@ Version, ) from .data_catalog import DataCatalog -from .lambda_dataset import LambdaDataSet, LambdaDataset -from .memory_dataset import MemoryDataSet, MemoryDataset +from .lambda_dataset import LambdaDataset, LambdaDataSet +from .memory_dataset import MemoryDataset, MemoryDataSet from .partitioned_dataset import ( - IncrementalDataSet, IncrementalDataset, - PartitionedDataSet, + IncrementalDataSet, PartitionedDataset, + PartitionedDataSet, ) # https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 diff --git a/kedro/io/cached_dataset.py b/kedro/io/cached_dataset.py index 21e60ab3ef..ed03983b37 100644 --- a/kedro/io/cached_dataset.py +++ b/kedro/io/cached_dataset.py @@ -5,11 +5,14 @@ from __future__ import annotations import logging +import warnings from typing import Any from kedro.io.core import VERSIONED_FLAG_KEY, AbstractDataSet, Version from kedro.io.memory_dataset import MemoryDataset -from kedro.utils import DeprecatedClassMeta + +# https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 +CachedDataSet: AbstractDataSet class CachedDataset(AbstractDataSet): @@ -120,7 +123,14 @@ def __getstate__(self): return self.__dict__ -class CachedDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=missing-class-docstring, too-few-public-methods - - _DeprecatedClassMeta__alias = CachedDataset +def __getattr__(name): + if name == "CachedDataSet": + alias = CachedDataset + warnings.warn( + f"{repr(name)} has been renamed to {repr(alias.__name__)}, " + f"and the alias will be removed in Kedro 0.19.0", + DeprecationWarning, + stacklevel=2, + ) + return alias + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(name)}") diff --git a/kedro/io/core.py b/kedro/io/core.py index fd90d25b8f..c0e6ee48e0 100644 --- a/kedro/io/core.py +++ b/kedro/io/core.py @@ -22,8 +22,6 @@ from kedro.utils import load_obj -warnings.simplefilter("default", DeprecationWarning) - VERSION_FORMAT = "%Y-%m-%dT%H.%M.%S.%fZ" VERSIONED_FLAG_KEY = "versioned" VERSION_KEY = "version" diff --git a/kedro/io/lambda_dataset.py b/kedro/io/lambda_dataset.py index 7dc96df75e..70378eac92 100644 --- a/kedro/io/lambda_dataset.py +++ b/kedro/io/lambda_dataset.py @@ -4,10 +4,13 @@ """ from __future__ import annotations +import warnings from typing import Any, Callable from kedro.io.core import AbstractDataSet, DatasetError -from kedro.utils import DeprecatedClassMeta + +# https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 +LambdaDataSet: AbstractDataSet class LambdaDataset(AbstractDataSet): @@ -121,7 +124,14 @@ def __init__( self.metadata = metadata -class LambdaDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=missing-class-docstring, too-few-public-methods - - _DeprecatedClassMeta__alias = LambdaDataset +def __getattr__(name): + if name == "LambdaDataSet": + alias = LambdaDataset + warnings.warn( + f"{repr(name)} has been renamed to {repr(alias.__name__)}, " + f"and the alias will be removed in Kedro 0.19.0", + DeprecationWarning, + stacklevel=2, + ) + return alias + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(name)}") diff --git a/kedro/io/memory_dataset.py b/kedro/io/memory_dataset.py index 349523c5d3..808fac8f81 100644 --- a/kedro/io/memory_dataset.py +++ b/kedro/io/memory_dataset.py @@ -3,13 +3,16 @@ from __future__ import annotations import copy +import warnings from typing import Any from kedro.io.core import AbstractDataSet, DatasetError -from kedro.utils import DeprecatedClassMeta _EMPTY = object() +# https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 +MemoryDataSet: AbstractDataSet + class MemoryDataset(AbstractDataSet): """``MemoryDataset`` loads and saves data from/to an in-memory @@ -139,7 +142,14 @@ def _copy_with_mode(data: Any, copy_mode: str) -> Any: return copied_data -class MemoryDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=missing-class-docstring, too-few-public-methods - - _DeprecatedClassMeta__alias = MemoryDataset +def __getattr__(name): + if name == "MemoryDataSet": + alias = MemoryDataset + warnings.warn( + f"{repr(name)} has been renamed to {repr(alias.__name__)}, " + f"and the alias will be removed in Kedro 0.19.0", + DeprecationWarning, + stacklevel=2, + ) + return alias + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(name)}") diff --git a/kedro/io/partitioned_dataset.py b/kedro/io/partitioned_dataset.py index 07b34ca65c..67ab51df3d 100644 --- a/kedro/io/partitioned_dataset.py +++ b/kedro/io/partitioned_dataset.py @@ -4,10 +4,10 @@ from __future__ import annotations import operator +import warnings from copy import deepcopy from typing import Any, Callable from urllib.parse import urlparse -from warnings import warn from cachetools import Cache, cachedmethod @@ -19,7 +19,7 @@ parse_dataset_definition, ) from kedro.io.data_catalog import CREDENTIALS_KEY -from kedro.utils import DeprecatedClassMeta, load_obj +from kedro.utils import load_obj DATASET_CREDENTIALS_KEY = "dataset_credentials" CHECKPOINT_CREDENTIALS_KEY = "checkpoint_credentials" @@ -31,6 +31,10 @@ S3_PROTOCOLS = ("s3", "s3a", "s3n") +# https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 +PartitionedDataSet: AbstractDataSet +IncrementalDataSet: AbstractDataSet + class PartitionedDataset(AbstractDataSet): # pylint: disable=too-many-instance-attributes,protected-access @@ -230,7 +234,7 @@ def __init__( # pylint: disable=too-many-arguments self._filepath_arg = filepath_arg if self._filepath_arg in self._dataset_config: - warn( + warnings.warn( f"'{self._filepath_arg}' key must not be specified in the dataset " f"definition as it will be overwritten by partition path" ) @@ -263,10 +267,11 @@ def _list_partitions(self) -> list[str]: ] def _join_protocol(self, path: str) -> str: - if self._path.startswith(self._protocol) and not path.startswith( - self._protocol + protocol_prefix = f"{self._protocol}://" + if self._path.startswith(protocol_prefix) and not path.startswith( + protocol_prefix ): - return f"{self._protocol}://{path}" + return f"{protocol_prefix}{path}" return path def _partition_to_path(self, path: str): @@ -337,12 +342,6 @@ def _release(self) -> None: self._invalidate_caches() -class PartitionedDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=missing-class-docstring, too-few-public-methods - - _DeprecatedClassMeta__alias = PartitionedDataset - - class IncrementalDataset(PartitionedDataset): """``IncrementalDataset`` inherits from ``PartitionedDataset``, which loads and saves partitioned file-like data using the underlying dataset @@ -559,8 +558,20 @@ def confirm(self) -> None: self._checkpoint.save(partition_ids[-1]) # checkpoint to last partition -class IncrementalDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=missing-class-docstring, too-few-public-methods +_DEPRECATED_ERROR_CLASSES = { + "PartitionedDataSet": PartitionedDataset, + "IncrementalDataSet": IncrementalDataset, +} + - # pylint: disable=unused-private-member - __DeprecatedClassMeta__alias = IncrementalDataset +def __getattr__(name): + if name in _DEPRECATED_ERROR_CLASSES: + alias = _DEPRECATED_ERROR_CLASSES[name] + warnings.warn( + f"{repr(name)} has been renamed to {repr(alias.__name__)}, " + f"and the alias will be removed in Kedro 0.19.0", + DeprecationWarning, + stacklevel=2, + ) + return alias + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(name)}") diff --git a/kedro/runner/parallel_runner.py b/kedro/runner/parallel_runner.py index fd0fa7d097..542dc74d63 100644 --- a/kedro/runner/parallel_runner.py +++ b/kedro/runner/parallel_runner.py @@ -7,6 +7,7 @@ import os import pickle import sys +import warnings from collections import Counter from concurrent.futures import FIRST_COMPLETED, ProcessPoolExecutor, wait from itertools import chain @@ -27,11 +28,13 @@ from kedro.pipeline import Pipeline from kedro.pipeline.node import Node from kedro.runner.runner import AbstractRunner, run_node -from kedro.utils import DeprecatedClassMeta # see https://github.com/python/cpython/blob/master/Lib/concurrent/futures/process.py#L114 _MAX_WINDOWS_WORKERS = 61 +# https://github.com/pylint-dev/pylint/issues/4300#issuecomment-1043601901 +_SharedMemoryDataSet: Any + class _SharedMemoryDataset: """``_SharedMemoryDataset`` is a wrapper class for a shared MemoryDataset in SyncManager. @@ -70,10 +73,17 @@ def save(self, data: Any): raise exc -class _SharedMemoryDataSet(metaclass=DeprecatedClassMeta): - # pylint: disable=too-few-public-methods - - _DeprecatedClassMeta__alias = _SharedMemoryDataset +def __getattr__(name): + if name == "_SharedMemoryDataSet": + alias = _SharedMemoryDataset + warnings.warn( + f"{repr(name)} has been renamed to {repr(alias.__name__)}, " + f"and the alias will be removed in Kedro 0.19.0", + DeprecationWarning, + stacklevel=2, + ) + return alias + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(name)}") class ParallelRunnerManager(SyncManager): diff --git a/kedro/utils.py b/kedro/utils.py index 380372853c..6067d96b6c 100644 --- a/kedro/utils.py +++ b/kedro/utils.py @@ -3,7 +3,6 @@ """ import importlib from typing import Any -from warnings import warn def load_obj(obj_path: str, default_obj_path: str = "") -> Any: @@ -27,67 +26,3 @@ def load_obj(obj_path: str, default_obj_path: str = "") -> Any: if not hasattr(module_obj, obj_name): raise AttributeError(f"Object '{obj_name}' cannot be loaded from '{obj_path}'.") return getattr(module_obj, obj_name) - - -class DeprecatedClassMeta(type): - """Metaclass for constructing deprecated aliases of renamed classes. - - Code implementation copied from https://stackoverflow.com/a/52087847 - """ - - def __new__(mcs, name, bases, classdict, *args, **kwargs): - alias = classdict.get("_DeprecatedClassMeta__alias") - - if alias is not None: - - def new(mcs, *args, **kwargs): - alias = getattr(mcs, "_DeprecatedClassMeta__alias") - - if alias is not None: - warn( - f"{repr(mcs.__name__)} has been renamed to " - f"{repr(alias.__name__)}, and the alias will " - f"be removed in Kedro 0.19.0", - DeprecationWarning, - stacklevel=2, - ) - - return alias(*args, **kwargs) - - classdict["__new__"] = new - classdict["_DeprecatedClassMeta__alias"] = alias - - fixed_bases = [] - - for base in bases: - alias = getattr(base, "_DeprecatedClassMeta__alias", None) - - if alias is not None: - warn( - f"{repr(base.__name__)} has been renamed to " - f"{repr(alias.__name__)}, and the alias will be " - f"removed in Kedro 0.19.0", - DeprecationWarning, - stacklevel=2, - ) - - # Avoid duplicate base classes. - base = alias or base - if base not in fixed_bases: - fixed_bases.append(base) - - fixed_bases = tuple(fixed_bases) - - return super().__new__(mcs, name, fixed_bases, classdict, *args, **kwargs) - - def __instancecheck__(cls, instance): - return any( - # pylint: disable=no-value-for-parameter - cls.__subclasscheck__(c) - for c in [type(instance), instance.__class__] - ) - - def __subclasscheck__(cls, subclass): - return subclass is cls or issubclass( - subclass, getattr(cls, "_DeprecatedClassMeta__alias") - ) diff --git a/pyproject.toml b/pyproject.toml index 8cdf924f8e..ec06b75d5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,7 @@ [build-system] # Minimum requirements for the build system to execute. requires = ["setuptools>=65.5.1"] # PEP 518 specifications +build-backend = "setuptools.build_meta" [project] name = "kedro" diff --git a/setup.py b/setup.py index 1d63d779cf..92eca04e5a 100644 --- a/setup.py +++ b/setup.py @@ -104,7 +104,7 @@ def _collect_requirements(requires): "sphinxcontrib-mermaid~=0.7.1", "myst-parser~=1.0.0", "Jinja2<3.1.0", - "kedro-datasets[all]~=1.4.0", + "kedro-datasets[all]~=1.4.2", ], "geopandas": _collect_requirements(geopandas_require), "matplotlib": _collect_requirements(matplotlib_require), diff --git a/test_requirements.txt b/test_requirements.txt index 4683509a8b..5c81ebdc89 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -40,7 +40,7 @@ pandas~=1.3 # 1.3 for read_xml/to_xml Pillow~=9.0 plotly>=4.8.0, <6.0 pre-commit>=2.9.2, <3.0 # The hook `mypy` requires pre-commit version 2.9.2. -psutil==5.8.0 +psutil~=5.8 pyarrow>=1.0, <7.0 pylint>=2.17.0, <3.0 pyproj~=3.0 diff --git a/tests/io/test_partitioned_dataset.py b/tests/io/test_partitioned_dataset.py index 05993593ae..97735a7380 100644 --- a/tests/io/test_partitioned_dataset.py +++ b/tests/io/test_partitioned_dataset.py @@ -471,6 +471,19 @@ def test_load_s3a(self, mocked_csvs_in_s3, partitioned_data_pandas, mocker): ] mocked_ds.assert_has_calls(expected, any_order=True) + @pytest.mark.parametrize( + "partition_path", ["s3_bucket/dummy.csv", "fake_bucket/dummy.csv"] + ) + def test_join_protocol_with_bucket_name_startswith_protocol( + self, mocked_csvs_in_s3, partition_path + ): + """Make sure protocol is joined correctly for the edge case when + bucket name starts with the protocol name, i.e. `s3://s3_bucket/dummy_.txt` + """ + + pds = PartitionedDataset(mocked_csvs_in_s3, "pandas.CSVDataSet") + assert pds._join_protocol(partition_path) == f"s3://{partition_path}" + @pytest.mark.parametrize("dataset", S3_DATASET_DEFINITION) def test_save(self, dataset, mocked_csvs_in_s3): pds = PartitionedDataset(mocked_csvs_in_s3, dataset) diff --git a/tests/runner/test_parallel_runner.py b/tests/runner/test_parallel_runner.py index 5b5d37c98a..6870561d8d 100644 --- a/tests/runner/test_parallel_runner.py +++ b/tests/runner/test_parallel_runner.py @@ -1,5 +1,6 @@ from __future__ import annotations +import importlib import sys from concurrent.futures.process import ProcessPoolExecutor from typing import Any @@ -33,6 +34,12 @@ ) +def test_deprecation(): + class_name = "_SharedMemoryDataSet" + with pytest.warns(DeprecationWarning, match=f"{repr(class_name)} has been renamed"): + getattr(importlib.import_module("kedro.runner.parallel_runner"), class_name) + + @pytest.mark.skipif( sys.platform.startswith("win"), reason="Due to bug in parallel runner" ) diff --git a/tests/test_utils.py b/tests/test_utils.py index b6ebe9e95a..4e99f3f726 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,7 +2,7 @@ import pytest -from kedro.utils import DeprecatedClassMeta, load_obj +from kedro.utils import load_obj # pylint: disable=too-few-public-methods @@ -28,53 +28,3 @@ def test_load_obj_invalid_attribute(self): def test_load_obj_invalid_module(self): with pytest.raises(ImportError, match=r"No module named 'missing_path'"): load_obj("InvalidClass", "missing_path") - - -class NewClass: - value = 1 - - -class NewClassSubclass(NewClass): - pass - - -class DeprecatedClass(metaclass=DeprecatedClassMeta): - _DeprecatedClassMeta__alias = NewClass - - -class DeprecatedClassSubclass(DeprecatedClass): - value = 2 - - -class DeprecatedClassSubSubclass(DeprecatedClassSubclass): - value = 3 - - -class TestDeprecatedClassMeta: - def test_is_subclass_of_deprecated_class(self): - assert issubclass(DeprecatedClass, DeprecatedClass) - assert issubclass(DeprecatedClassSubclass, DeprecatedClass) - assert issubclass(DeprecatedClassSubSubclass, DeprecatedClass) - assert issubclass(NewClass, DeprecatedClass) - assert issubclass(NewClassSubclass, DeprecatedClass) - - def test_is_subclass_of_new_class(self): - assert issubclass(DeprecatedClassSubclass, NewClass) - assert issubclass(DeprecatedClassSubSubclass, NewClass) - - def test_is_instance_of_deprecated_class(self): - assert isinstance(DeprecatedClass(), DeprecatedClass) - assert isinstance(DeprecatedClassSubclass(), DeprecatedClass) - assert isinstance(DeprecatedClassSubSubclass(), DeprecatedClass) - assert isinstance(NewClass(), DeprecatedClass) - assert isinstance(NewClassSubclass(), DeprecatedClass) - - def test_is_instance_of_new_class(self): - assert isinstance(DeprecatedClassSubclass(), NewClass) - assert isinstance(DeprecatedClassSubSubclass(), NewClass) - - def test_inheritance(self): - assert NewClass().value == 1 - assert DeprecatedClass().value == 1 # pylint: disable=no-member - assert DeprecatedClassSubclass().value == 2 - assert DeprecatedClassSubSubclass().value == 3