Repository: travel-tools/travel (language: python)

Issue:
Feat/plans



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/piper.yml b/.github/workflows/travel.yml
similarity index 73%
rename from .github/workflows/piper.yml
rename to .github/workflows/travel.yml
index 692d0c3..e81b043 100644
--- a/.github/workflows/piper.yml
+++ b/.github/workflows/travel.yml
@@ -1,4 +1,4 @@
-name: Piper
+name: Travel
 on:
   pull_request:
     types: [ opened, synchronize, reopened ]
@@ -74,54 +74,55 @@ jobs:
         run: devops\${{ github.event.repository.name }}\test.bat
         shell: cmd
 
-  release-package-on-test:
-    if: ${{ github.base_ref == 'master' && ( startsWith(github.head_ref, 'release/major/') || startsWith(github.head_ref, 'release/minor/') || startsWith(github.head_ref, 'hotfix/major/') || startsWith(github.head_ref, 'hotfix/minor/') ) }}
-    environment: test-pypi
-    needs: test-package
-    runs-on: ubuntu-latest
-    steps:
-      - name: Setup Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: '3.7.11'
-      - name: Download dist
-        uses: actions/download-artifact@v2
-        with:
-          name: dist
-          path: ${{ github.workspace }}/dist
-      - name: Release on Test
-        uses: pypa/gh-action-pypi-publish@release/v1
-        with:
-          user: __token__
-          password: ${{ secrets.PYPI_TOKEN }}
-          repository_url: https://test.pypi.org/legacy/
-
-  test-package-on-test-pypi:
-    needs: [release-package-on-test, pack]
-    strategy:
-      matrix:
-        os: [macos-latest, ubuntu-latest, windows-latest]
-        python: [3.7, 3.8, 3.9]
-    runs-on: ${{ matrix.os }}
-    steps:
-      - name: Setup Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.python }}
-      - name: Install package
-        run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ piper-tools==${{ needs.pack.outputs.version }}
-      - uses: actions/checkout@v2
-      - run: pip install pytest==7.1.1
-      - name: Test Unix
-        if: ${{ matrix.os != 'windows-latest' }}
-        run: source devops/${{ github.event.repository.name }}/test.sh
-      - name: Test Windows
-        if: ${{ matrix.os == 'windows-latest' }}
-        run: devops\${{ github.event.repository.name }}\test.bat
-        shell: cmd
+#  release-package-on-test:
+#    if: ${{ github.base_ref == 'master' && ( startsWith(github.head_ref, 'release/major/') || startsWith(github.head_ref, 'release/minor/') || startsWith(github.head_ref, 'hotfix/major/') || startsWith(github.head_ref, 'hotfix/minor/') ) }}
+#    environment: test-pypi
+#    needs: test-package
+#    runs-on: ubuntu-latest
+#    steps:
+#      - name: Setup Python
+#        uses: actions/setup-python@v2
+#        with:
+#          python-version: '3.7.11'
+#      - name: Download dist
+#        uses: actions/download-artifact@v2
+#        with:
+#          name: dist
+#          path: ${{ github.workspace }}/dist
+#      - name: Release on Test
+#        uses: pypa/gh-action-pypi-publish@release/v1
+#        with:
+#          user: __token__
+#          password: ${{ secrets.PYPI_TOKEN }}
+#          repository_url: https://test.pypi.org/legacy/
+#
+#  test-package-on-test-pypi:
+#    needs: [release-package-on-test, pack]
+#    strategy:
+#      matrix:
+#        os: [macos-latest, ubuntu-latest, windows-latest]
+#        python: [3.7, 3.8, 3.9]
+#    runs-on: ${{ matrix.os }}
+#    steps:
+#      - name: Setup Python
+#        uses: actions/setup-python@v2
+#        with:
+#          python-version: ${{ matrix.python }}
+#      - name: Install package
+#        run: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/travel==${{ needs.pack.outputs.version }}
+#      - uses: actions/checkout@v2
+#      - run: pip install pytest==7.1.1
+#      - name: Test Unix
+#        if: ${{ matrix.os != 'windows-latest' }}
+#        run: source devops/${{ github.event.repository.name }}/test.sh
+#      - name: Test Windows
+#        if: ${{ matrix.os == 'windows-latest' }}
+#        run: devops\${{ github.event.repository.name }}\test.bat
+#        shell: cmd
 
   new-version:
-    needs: [test-package-on-test-pypi, pack]
+    if: ${{ github.base_ref == 'master' && ( startsWith(github.head_ref, 'release/major/') || startsWith(github.head_ref, 'release/minor/') || startsWith(github.head_ref, 'hotfix/major/') || startsWith(github.head_ref, 'hotfix/minor/') ) }}
+    needs: [test-package]
     runs-on: ubuntu-latest
     steps:
       - name: Setup Python
@@ -136,16 +137,10 @@ jobs:
       - name: Checkout from branch
         uses: actions/checkout@v2
         if: github.event_name != 'pull_request'
-      - name: Change version
-        run: python devops/github/replace.py src/${{ github.event.repository.name }}/package/setup.py version=.+, version=\"${{ needs.pack.outputs.version }}\",
       - name: Configure
         run: |
           git config --global user.name 'GitHub Actions'
           git config --global user.email 'actions@users.noreply.github.com'
-      - name: Commit
-        run: |
-          git commit -am "[skip ci] Version ${{ needs.pack.outputs.version }}"
-          git push
       - name: Tag
         run: |
           git tag ${{ needs.pack.outputs.version }}
@@ -177,7 +172,7 @@ jobs:
   ##########################################
 
   build-docker:
-    needs: test-package
+    needs: pack
     runs-on: ubuntu-latest
     steps:
       - name: Set up Docker Buildx
diff --git a/README.md b/README.md
index f8d3ad8..3f3227d 100644
--- a/README.md
+++ b/README.md
@@ -1,70 +1,82 @@
-# Piper
+# Travel
 
 A software manager for easy **development** and **distribution** of **Python code**.
 
-The main features that Piper adds to Python are:
+The main features that Travel adds to Python are:
 
 - Support for large-scale, **multi-package projects**
 - **Reproducibility** (clear, transparent **dependency management**)
-- Robust **development-lifecycle**, from blueprinting to distribution 
+- Robust **development-lifecycle**, from blueprinting to distribution
+- Possibility to have different scoped Virtual Environment (e.g. for development, for testing, for notebooks...) with additional requirements
 
-Piper is inspired by what **[Maven](https://maven.apache.org/)** is for Java and uses [Pip](https://github.com/pypa/pip) and [Virtual Environments](https://docs.python.org/3/library/venv.html).
+Travel is inspired by what **[Maven](https://maven.apache.org/)** is for Java and uses [Pip](https://github.com/pypa/pip) and [Virtual Environments](https://docs.python.org/3/library/venv.html).
 
-## Why Piper
+## Why Travel
 
-**Python is great** in many things, particularly for **scripting**. But it is powerful enough to create **complex software** too. Still, when doing so, it lacks some robustness and quickness.
+**Python is great** at many things, particularly for **scripting**. But it is powerful enough to create **complex software** too. Still, when doing so, it lacks some robustness and quickness.
 
 Instead:
  
-- Piper lets you **forget about repetitive setup of Python projects**, with creation of Virtual Environments, issues with imports, PYTHONPATH, folder structures. It does all of this for you.
+- Travel lets you **forget about repetitive setup of Python projects**, with creation of Virtual Environments, issues with imports, PYTHONPATH, folder structures. It does all of this for you.
 
-- Piper ensures that when you run a Python script, all its **requirements are implicitly installed**. Along with reproducibility, this makes room for **easy collaboration** between developers.
+- Travel ensures that when you run a Python script, all its **requirements are implicitly installed**. Along with reproducibility, this makes room for **easy collaboration** between developers.
 
-- Also, while with standard tools it's hard to have multiple packages, one requiring the other, with Piper is a matter of **few YAML files**. You can **split your project** in several packages instead of having tons of requirements, code replication and/or single package-monoliths. 
+- Also, while with standard tools it's hard to have multiple packages, one requiring another, with Travel this is a matter of **few YAML files**. You can **split your project** in several packages instead of having tons of requirements, code replication and/or single package-monoliths. 
 
-All in all, Piper lets you design **proper structure and modularity** for your code. No worries about imports and low-level stuff.
+All in all, Travel lets you design **proper structure and modularity** for your code. No worries about imports and low-level stuff.
 
-## Example of Piper Project
+## Example of Travel Project
 
-You can find an example of Piper project [here](https://github.com/piper-tools/piper/tree/master/src/piper/tests/data/complexproject).
+You can find an example of Travel project [here](https://github.com/travel-tools/travel/tree/master/src/travel/tests/data/complexproject).
 
 ## Installation
 
 With Python >= 3.7, simply run
 
 ```
-pip install piper-tools
+pip install travel
 ```
 
-From now on, you can use `piper` from command line.
+From now on, you can use `travel` from command line.
+
+
+## Create a new project or add a new Bag
+
+Travel supports several packages ("Bags") in a single project, nested in other folders ("Bags containing Bags"). You can create a project using [Travel Plans](https://github.com/travel-tools/cookiecutter-travelplan).
+
+You can also create a new Bag after the first plan using `travel add`, to add a new [Travel Plan](https://github.com/travel-tools/cookiecutter-travelplan).
+
+For a quick understanding, see below.
 
 ## Basic Usage
 
-Once you have defined your structure of Pipes (submodules of your project), for instance like in the [example](https://github.com/piper-tools/piper/tree/master/src/piper/tests/data/complexproject) or like this
+To see how to create your first Travel project, see the section above.
+
+Once you have defined your structure of Bags (submodules of your project), for instance like in the [example](https://github.com/travel-tools/travel/tree/master/src/travel/tests/data/complexproject) or like this
 
 ```
 complexproject/
     common/
         package/
-        pipe.yml
+        bag.yml
     microservices/
         first/
             package/
-            pipe.yml
+            bag.yml
         second/
             package/
-            pipe.yml
-        pipe.yml
-    pipe.yml
+            bag.yml
+        bag.yml
+    bag.yml
 ```
 
 you can run
 
 ```
-piper setup
+travel setup
 ```
 
-in the main folder, where the first `pipe.yml` file is located.
+in the main folder, where the first `bag.yml` file is located.
 
 This will:
 
@@ -74,18 +86,18 @@ This will:
 - Install the packages themselves in development mode (so that you can `import` them in their Virtual Environments)
 
 
-You can also create a distribution package with `piper pack [commands]`, which is the equivalent of `python setup.py [commands]`, for instance:
+You can also create a distribution package with `travel pack [commands]`, which is the equivalent of `python setup.py [commands]`, for instance:
 
 ```
-piper pack sdist
+travel pack sdist
 ```
 
-in the folder of the Pipe you want to pack. This will create the distribution in the `./build/package/dist` folder.
+in the folder of the Bag you want to pack. This will create the distribution in the `./build/package/dist` folder.
 
 In any moment, you can run
 
 ```
-piper clean
+travel clean
 ```
 
-to destroy the Virtual Environments and other build objects.
\ No newline at end of file
+to destroy the Virtual Environments and other build objects.
diff --git a/containers/piper/docker-compose.dist.yml b/containers/piper/docker-compose.dist.yml
deleted file mode 100644
index d98932d..0000000
--- a/containers/piper/docker-compose.dist.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-version: "3"
-
-services:
-
-  piper:
-
-    build:
-      context: "./../.."
-      dockerfile: "containers/piper/Dockerfile"
-      args:
-        - DIST=src/piper/package/dist/
-
-    image: "piper"
diff --git a/containers/piper/docker-compose.yml b/containers/piper/docker-compose.yml
deleted file mode 100644
index 9996e3e..0000000
--- a/containers/piper/docker-compose.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-version: "3"
-
-services:
-
-  piper:
-
-    build:
-      context: "./../.."
-      dockerfile: "containers/piper/Dockerfile.source"
-
-    image: "piper"
diff --git a/containers/piper/Dockerfile b/containers/travel/Dockerfile
similarity index 77%
rename from containers/piper/Dockerfile
rename to containers/travel/Dockerfile
index d37c7e3..c26a0f1 100644
--- a/containers/piper/Dockerfile
+++ b/containers/travel/Dockerfile
@@ -4,7 +4,7 @@ ARG TAG="3.7.4"
 FROM python:${TAG}
 
 # Copy the dist
-ARG DIST="src/piper/package/dist"
+ARG DIST="src/travel/package/dist"
 COPY ${DIST} dist/
 
 # Install the package
diff --git a/containers/piper/Dockerfile.source b/containers/travel/Dockerfile.source
similarity index 84%
rename from containers/piper/Dockerfile.source
rename to containers/travel/Dockerfile.source
index 2b59fe4..73f96ad 100644
--- a/containers/piper/Dockerfile.source
+++ b/containers/travel/Dockerfile.source
@@ -1,6 +1,6 @@
 FROM python:3.7.4
 
-ARG SRC="src/piper/package"
+ARG SRC="src/travel/package"
 WORKDIR application
 
 # Pre-install requirements, so that this sub-image is cached - no need to re-download it upon code-only change.
@@ -9,7 +9,7 @@ RUN pip install --no-cache-dir -r requirements.txt
 
 # Prepare package installation copying sources
 COPY ${SRC}/setup.py ${SRC}/MANIFEST.in ${SRC}/README.md .
-COPY ${SRC}/piper piper/
+COPY ${SRC}/travel travel/
 
 # Install the package
 RUN pip install -e .
diff --git a/containers/travel/docker-compose.dist.yml b/containers/travel/docker-compose.dist.yml
new file mode 100644
index 0000000..a559738
--- /dev/null
+++ b/containers/travel/docker-compose.dist.yml
@@ -0,0 +1,13 @@
+version: "3"
+
+services:
+
+  travel:
+
+    build:
+      context: "./../.."
+      dockerfile: "containers/travel/Dockerfile"
+      args:
+        - DIST=src/travel/package/dist/
+
+    image: "travel"
diff --git a/containers/travel/docker-compose.yml b/containers/travel/docker-compose.yml
new file mode 100644
index 0000000..f289552
--- /dev/null
+++ b/containers/travel/docker-compose.yml
@@ -0,0 +1,11 @@
+version: "3"
+
+services:
+
+  travel:
+
+    build:
+      context: "./../.."
+      dockerfile: "containers/travel/Dockerfile.source"
+
+    image: "travel"
diff --git a/devops/github/registry.py b/devops/github/registry.py
index 3fdfad1..b700a01 100644
--- a/devops/github/registry.py
+++ b/devops/github/registry.py
@@ -52,7 +52,7 @@ class Action(str, Enum):
     artifact = args.artifact
 
     registry = GitHubRegistry(
-        organization="piper-tools",
+        organization="travel-tools",
         type=args.type,
         personal_access_token=("federicopugliese", os.environ["REGISTRY_PAT"])
     )
diff --git a/src/piper/package/README.md b/src/piper/package/README.md
deleted file mode 100644
index a7ded35..0000000
--- a/src/piper/package/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Piper
-
-A software manager for easy **development** and **distribution** of **Python code**.
-
-The main features that Piper adds to Python are:
-
-- Support for large-scale, **multi-package projects**
-- **Reproducibility** (clear, transparent **dependency management**)
-- Robust **development-lifecycle**, from blueprinting to distribution 
-
-Piper is inspired by what **[Maven](https://maven.apache.org/)** is for Java and uses [Pip](https://github.com/pypa/pip) and [Virtual Environments](https://docs.python.org/3/library/venv.html).
-
-## Why Piper
-
-**Python is great** in many things, particularly for **scripting**. But it is powerful enough to create **complex software** too. Still, when doing so, it lacks some robustness and quickness.
-
-Instead:
- 
-- Piper lets you **forget about repetitive setup of Python projects**, with creation of Virtual Environments, issues with imports, PYTHONPATH, folder structures. It does all of this for you.
-
-- Piper ensures that when you run a Python script, all its **requirements are implicitly installed**. Along with reproducibility, this makes room for **easy collaboration** between developers.
-
-- Also, while with standard tools it's hard to have multiple packages, one requiring the other, with Piper is a matter of **few YAML files**. You can **split your project** in several packages instead of having tons of requirements, code replication and/or single package-monoliths. 
-
-All in all, Piper lets you design **proper structure and modularity** for your code. No worries about imports and low-level stuff.
-
-## Example of Piper Project
-
-You can find an example of Piper project [here](https://github.com/piper-tools/piper/tree/master/src/piper/tests/data/complexproject).
\ No newline at end of file
diff --git a/src/piper/package/piper/__main__.py b/src/piper/package/piper/__main__.py
deleted file mode 100644
index 8d86164..0000000
--- a/src/piper/package/piper/__main__.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import argparse
-import logging
-import os
-
-from piper.cli import python_wrapper, blueprinter, packer
-from piper.cli.cleaner import Cleaner
-from piper.cli.setupper import Setupper
-
-logger = logging.getLogger(__name__)
-
-
-def main():
-
-    # Piper
-    parser = argparse.ArgumentParser()
-    parser.add_argument("--context", default=os.getcwd(), help="Path to the folder containing the pipe.yml file")
-    parser.set_defaults(action=lambda args: parser.parse_args(["-h"]))
-    subparsers = parser.add_subparsers()
-
-    # Clean
-    clean = subparsers.add_parser("clean")
-    clean.set_defaults(action=lambda args, rest: Cleaner().manage(args.context))
-
-    # Blueprint
-    blueprint = subparsers.add_parser("blueprint")
-    blueprint.add_argument("--local-blueprints", help="Extra folders containing the main pipe.yml of the local blueprints to use", nargs="+", required=False)
-    blueprint.set_defaults(action=lambda args, rest: blueprinter.run(args.context, local_blueprints=args.local_blueprints))
-
-    # Setup
-    setup = subparsers.add_parser("setup")
-    setup.set_defaults(action=lambda args, rest: Setupper().manage(args.context))
-
-    # Pack
-    pack = subparsers.add_parser("pack")
-    pack.add_argument("--package", help="Name of the pipe to run setup.py commands", required=False)
-    pack.set_defaults(action=lambda args, rest: packer.pack(args.context, rest, package=args.package))
-
-    # Release
-
-    # # Python
-    # python = subparsers.add_parser("python")
-    # python.add_argument("package", help="Name of the pipe (it will be used to activate its venv)")
-    # python.set_defaults(action=lambda args, rest: python_wrapper.run(args.context, args.package, rest))
-
-    # Parse args and invoke the corresponding functions
-    arguments, remainder = parser.parse_known_args()
-    arguments.action(arguments, remainder)
-    logger.info("All done.")
-
-
-if __name__ == '__main__':
-    main()
diff --git a/src/piper/package/piper/cli/base.py b/src/piper/package/piper/cli/base.py
deleted file mode 100644
index 99add8a..0000000
--- a/src/piper/package/piper/cli/base.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import abc
-import logging
-from typing import Set
-
-from piper.config.pipe import Pipe
-from piper.config.reader import get_pipe_name, read_all_pipes
-from piper.custom.tasks import performer
-from piper.tools.python import main_python, Python
-
-logger = logging.getLogger(__name__)
-
-
-class PiperCommand(abc.ABC):
-
-    def __init__(self, python: Python = main_python):
-        self.main_python = python
-
-    @abc.abstractmethod
-    def _phase_name(self) -> str:
-        pass
-
-    def _perform_tasks(self, pipe: Pipe, step: str):
-        return performer.perform_tasks(self._phase_name(), step, pipe)
-
-    @abc.abstractmethod
-    def _manage(self, pipe: Pipe):
-        pass
-
-    def _manage_from_pipe_recursive(self, pipe: Pipe, done: Set[Pipe]):
-
-        if not pipe.group:
-
-            # Manage dependencies first, then the pipe
-            for p in [*pipe.flat_dependencies(), pipe]:
-                if p not in done:
-                    logger.info("")
-                    logger.info("="*60)
-                    logger.info(f"=== {p.name.center(52, ' ')} ===")
-                    logger.info("="*60)
-                    self._perform_tasks(p, "pre")
-                    self._manage(p)
-                    self._perform_tasks(p, "post")
-                    logger.info("=" * 60)
-                    logger.info("")
-                    logger.info("")
-                    done.add(p)
-
-        else:
-
-            # Manage each element of the group
-            for pipe in pipe.group:
-                self._manage_from_pipe_recursive(pipe, done=done)
-
-    def manage_from_pipe(self, pipe: Pipe):
-        self._manage_from_pipe_recursive(pipe, done=set())
-
-    def manage(self, context: str, package: str = None) -> (Pipe, Pipe):
-
-        # Read the target pipe and all the pipes
-        target = package or get_pipe_name(context)
-        pipes = read_all_pipes(context)
-
-        # Manage this target pipe
-        if target not in pipes:
-            raise ValueError(f"The specified pipe does not exist: {context}")
-        pipe = pipes[target]
-        self.manage_from_pipe(pipe)
-        return pipe, pipes
diff --git a/src/piper/package/piper/cli/blueprinter.py b/src/piper/package/piper/cli/blueprinter.py
deleted file mode 100644
index a3a18f5..0000000
--- a/src/piper/package/piper/cli/blueprinter.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import logging
-import os
-import shutil
-from pathlib import Path
-from typing import Dict, Any, List
-
-import pkg_resources
-import yaml
-from piper import RESOURCES_LOCATION
-from piper.config.pipe import Pipe
-from piper.config.reader import PIPE_FILE
-from piper.config.sanitizers import pip_sanitizer, name_sanitizer
-from piper.tools.venv import Virtualenv
-
-logger = logging.getLogger(__name__)
-_GENERATE_BLUEPRINT = os.path.join(RESOURCES_LOCATION, "blueprint", "generate.py")
-
-
-def _generate_breath_first(blueprint_file: str, venv: Virtualenv, context: str, yml: Dict[str, Any], local_blueprints: List[str] = None):
-
-    # Generate these blueprints
-    for pipe, properties in yml.items():
-
-        # Get the folder where the pipe will be created
-        pipe = name_sanitizer.sanitize_name(pipe)
-        pipe_context = os.path.join(context, pipe)
-
-        # Is it a blueprint?
-        if "blueprint" in properties:
-
-            # Get the package of the blueprint
-            blueprint = properties["blueprint"]
-            name = pip_sanitizer.sanitize_versioned_package(blueprint)
-            venv.pip.install(name, allow_pipes_from=local_blueprints)
-
-            # Generate the blueprint
-            command = f'"{_GENERATE_BLUEPRINT}" --context "{pipe_context}" --blueprint {name} --file "{blueprint_file}" --pipe {pipe}'
-            venv.python.run(command)
-
-    # Create the "folder" pipe.yml file
-    pipe_file = os.path.join(context, PIPE_FILE)
-    if not os.path.isfile(pipe_file):
-        Path(pipe_file).touch()
-
-    # Generate the subpipes
-    for pipe, properties in yml.items():
-        pipe_context = os.path.join(context, pipe)
-        if "pipes" in properties:
-            _generate_breath_first(blueprint_file, venv, pipe_context, properties["pipes"], local_blueprints=local_blueprints)
-
-
-def run(context: str, local_blueprints: List[str] = None):
-
-    # Temporary create a venv
-    venv = Virtualenv(Pipe(location=context, yml={}))
-    venv.create()
-    version = pip_sanitizer.sanitize_version(pkg_resources.get_distribution("PyYAML").version)
-    venv.pip.install(f"PyYAML=={version}")
-
-    # Read the blueprint file
-    path = os.path.join(context, "blueprint.yml")
-    with open(path) as f:
-        yml = yaml.load(f, Loader=yaml.SafeLoader)
-    if not yml or "pipes" not in yml:
-        raise ValueError("Blueprint file must not be empty and should contain at least 'pipes' key.")
-
-    # Generate
-    _generate_breath_first(path, venv, context, yml["pipes"], local_blueprints=local_blueprints)
-
-    # Remove the temporary venv
-    shutil.rmtree(venv.path)
diff --git a/src/piper/package/piper/cli/cleaner.py b/src/piper/package/piper/cli/cleaner.py
deleted file mode 100644
index b03ac48..0000000
--- a/src/piper/package/piper/cli/cleaner.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-import os
-import shutil
-
-from piper.cli.base import PiperCommand
-from piper.config.pipe import Pipe
-from piper.custom.scopes.scoped_venvs import ScopedVirtualenvs
-from piper.tools.venv import Virtualenv
-
-logger = logging.getLogger(__name__)
-
-
-class Cleaner(PiperCommand):
-
-    def _phase_name(self):
-        return "clean"
-
-    def _perform_tasks(self, pipe: Pipe, step: str):
-        if step == "post":
-            pass
-        else:
-            super()._perform_tasks(pipe, step)
-
-    def _manage(self, pipe: Pipe):
-
-        logger.info(f"Cleaning {pipe.name}")
-        to_remove = [
-            os.path.join(pipe.setup_py_folder, f"{pipe.package}.egg-info"),     # egg-info
-            Virtualenv(pipe).path,                                              # venv folder
-            pipe.build_folder                                                   # artifacts build folder
-        ]
-
-        # If with scopes, remove them too
-        if pipe.scopes:
-            scopes = ScopedVirtualenvs(pipe)
-            for env in scopes.envs.values():
-                to_remove.append(env.path)
-
-        # Clean
-        for path in to_remove:
-            if os.path.isdir(path):
-                shutil.rmtree(path)
-
diff --git a/src/piper/package/piper/cli/setupper.py b/src/piper/package/piper/cli/setupper.py
deleted file mode 100644
index f08b95c..0000000
--- a/src/piper/package/piper/cli/setupper.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-from pathlib import Path
-
-from piper.cli.base import PiperCommand
-from piper.config.pipe import Pipe
-from piper.custom.scopes.scoped_venvs import ScopedVirtualenvs
-from piper.tools.venv import Virtualenv
-
-
-class Setupper(PiperCommand):
-
-    def _phase_name(self) -> str:
-        return "setup"
-
-    def _manage(self, pipe: Pipe):
-
-        # Create the virtualenv
-        venv = Virtualenv(pipe, touch_requirements_file=True)
-        venv.create()
-        venv.update()
-
-        # Pip freeze
-        venv.freeze()
-
-        # Create the scopes
-        if pipe.scopes:
-            scopes = ScopedVirtualenvs(pipe, touch_requirements_file=True)
-            for scope in pipe.scopes:
-                scopes.create(scope)
-                scopes.update(scope)
-                scopes.freeze(scope)
-
diff --git a/src/piper/package/piper/config/reader.py b/src/piper/package/piper/config/reader.py
deleted file mode 100644
index a5f8268..0000000
--- a/src/piper/package/piper/config/reader.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import logging
-import os
-from pathlib import Path
-from typing import List, Dict
-
-import yaml
-from piper.config.pipe import Pipe
-
-PIPE_FILE = "pipe.yml"
-
-
-logger = logging.getLogger(__name__)
-
-
-def _read_pipe(location: str) -> Pipe:
-
-    # Read the pipe file
-    path = os.path.join(location, PIPE_FILE)
-    with open(path) as f:
-        yml = yaml.load(f, Loader=yaml.SafeLoader) or {}
-        return Pipe(location=location, yml=yml)
-
-
-def _has_pipe(location: str) -> bool:
-    return os.path.isfile(os.path.join(location, PIPE_FILE))
-
-
-def get_pipe_name(location: str) -> str:
-    return os.path.basename(os.path.normpath(location))
-
-
-def read_all_pipes(location: str) -> Dict[str, Pipe]:
-
-    # Find the uppermost (parent) pipe file
-    uppermost = location
-    while _has_pipe(location):
-        uppermost = location
-        location = str(Path(location).parent)
-
-    # Read main pipe file and nested pipe files
-    pipes = {pipe.name: pipe for pipe in _read_pipes_from(uppermost)}
-    # Set the root context
-    for p in pipes.values():
-        p.root_context = uppermost
-
-    # Build dependencies
-    for name in pipes:
-        for pipe in pipes.values():
-            if name in pipe.dependencies:
-                pipe.fill_dependency_with_pipe(pipes[name])
-    return pipes
-
-
-def _read_pipes_from(location: str) -> List[Pipe]:
-
-    # Read the local pipe
-    group = []
-    pipe = _read_pipe(location)
-
-    # Read all pipes recursively
-    pipes = [pipe]
-    for directory in os.listdir(location):
-        current = os.path.join(location, directory)
-        if _has_pipe(current):
-            children = _read_pipes_from(current)
-            group = group + children
-            pipes = pipes + children
-
-    # Set the nested group
-    pipe.group = group
-    return pipes
diff --git a/src/piper/package/piper/custom/scopes/scoped_venvs.py b/src/piper/package/piper/custom/scopes/scoped_venvs.py
deleted file mode 100644
index 2a4913b..0000000
--- a/src/piper/package/piper/custom/scopes/scoped_venvs.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import os
-
-from piper.config.pipe import Pipe
-from piper.tools.base_venv import BaseVirtualenv
-from piper.tools.python import Python
-from piper.tools.python import main_python as default_python
-
-
-class ScopedVirtualenvs():
-
-    def __init__(self, pipe: Pipe, main_python: Python = default_python, touch_requirements_file: bool = False):
-        self.envs = {
-            scope: BaseVirtualenv(
-                location=pipe.location,
-                name_suffix=f"{pipe.name}-{scope}",
-                pip_config=pipe.pip,
-                dependencies=pipe.flat_dependencies(with_current=True),
-                requirements_file=os.path.join(pipe.location, f"requirements_{scope}.txt"),
-                main_python=main_python,
-                touch_requirements_file=touch_requirements_file,
-                extra_requirements=config.requirements
-            )
-            for scope, config in pipe.scopes.items()
-        }
-
-    def create(self, scope: str):
-        self.envs[scope].create()
-
-    def create_all(self):
-        for scope in self.envs.keys():
-            self.create(scope)
-
-    def update(self, scope: str):
-        self.envs[scope].update()
-
-    def update_all(self):
-        for scope in self.envs.keys():
-            self.update(scope)
-
-    def freeze(self, scope: str):
-        self.envs[scope].freeze()
-
-    def freeze_all(self):
-        for scope in self.envs.keys():
-            self.freeze(scope)
diff --git a/src/piper/package/piper/resources/blueprint/generate.py b/src/piper/package/piper/resources/blueprint/generate.py
deleted file mode 100644
index 65ac4ee..0000000
--- a/src/piper/package/piper/resources/blueprint/generate.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import argparse
-import importlib
-import inspect
-import sys
-from pathlib import Path
-
-import yaml
-from piperblue.blueprint import PiperBlueprint
-
-
-def _find(yml, pipe):
-    try:
-        for p, properties in yml.items():
-            if p == pipe:
-                return properties
-            found = _find(yml[p], pipe)
-            if found:
-                return found
-    except:
-        return None
-
-
-def _generate(file: str, blueprint_name: str, pipe: str, context: str):
-
-    # Find the config
-    with open(file) as f:
-        yml = yaml.load(f, Loader=yaml.SafeLoader)
-    properties = _find(yml, pipe)
-    config = properties.get("config")
-
-    # Import the blueprint class
-    module = f"{blueprint_name}.blueprint"
-    all_blueprints = inspect.getmembers(
-        importlib.import_module(module),
-        lambda member: inspect.isclass(member) and member.__module__ == module and issubclass(member, PiperBlueprint)
-    )[0]
-    blueprint = all_blueprints[1]
-
-    # Get its resources folder
-    resources_folder = str(Path(sys.modules[blueprint.__module__].__file__).parent / "resources")
-
-    # Instantiate it and run it
-    blueprint().generate(resources_folder, context, config)
-
-
-def main():
-
-    # Parse
-    parser = argparse.ArgumentParser()
-    parser.add_argument("--file")
-    parser.add_argument("--context")
-    parser.add_argument("--pipe")
-    parser.add_argument("--blueprint")
-    args = parser.parse_args()
-
-    # Generate
-    _generate(args.file, args.blueprint, args.pipe, args.context)
-
-
-if __name__ == '__main__':
-
-    main()
diff --git a/src/piper/package/piper/tools/pip.py b/src/piper/package/piper/tools/pip.py
deleted file mode 100644
index eab0fbf..0000000
--- a/src/piper/package/piper/tools/pip.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from typing import List, Union
-
-from piper.config.reader import read_all_pipes
-from piper.config.sanitizers.pip_sanitizer import sanitize_package, sanitize_versioned_package, is_just_package
-from piper.tools.executable import Executable
-from piper.tools.python import Python
-
-
-class Pip(Executable):
-
-    def __init__(self, python: Python, pre_command: str = None):
-        super().__init__(f"{python.path} -m pip --disable-pip-version-check", pre_command=pre_command)
-
-    def install(self, requirements: Union[List[str], str], allow_pipes_from: Union[str, List[str]] = None):
-
-        if isinstance(requirements, str):
-            requirements = [requirements]
-
-        # Install real requirements
-        packages = [
-            f'{sanitize_versioned_package(req)}'
-            for req in requirements
-            if not is_just_package(req)
-        ]
-        if packages:
-            super().run(f"install {' '.join(packages)}")
-
-        # Install pipes
-        pipes = [
-            f'{sanitize_package(req)}'
-            for req in requirements
-            if is_just_package(req)
-        ]
-        if pipes:
-            if not allow_pipes_from:
-                raise ValueError(f"{pipes} without versions (or pipes) are not allowed here")
-            else:
-                allow_pipes_from = allow_pipes_from if isinstance(allow_pipes_from, list) else [allow_pipes_from]
-                for context in allow_pipes_from:
-                    all_pipes = read_all_pipes(context)
-                    super().run(f"install -e {' '.join([p.setup_py_folder for p in all_pipes.values() if p.name in pipes])}")
diff --git a/src/piper/package/piper/tools/venv.py b/src/piper/package/piper/tools/venv.py
deleted file mode 100644
index 4105950..0000000
--- a/src/piper/package/piper/tools/venv.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from piper.config.pipe import Pipe
-from piper.tools.base_venv import BaseVirtualenv
-from piper.tools.python import Python
-from piper.tools.python import main_python as default_python
-
-
-class Virtualenv(BaseVirtualenv):
-
-    def __init__(self, pipe: Pipe, main_python: Python = default_python, touch_requirements_file: bool = False):
-        super().__init__(
-            location=pipe.location,
-            name_suffix=pipe.name,
-            pip_config=pipe.pip,
-            dependencies=pipe.flat_dependencies(with_current=True),
-            requirements_file=pipe.requirements_file,
-            main_python=main_python,
-            touch_requirements_file=touch_requirements_file
-        )
diff --git a/src/piper/package/requirements.txt b/src/piper/package/requirements.txt
deleted file mode 100644
index 81fdc38..0000000
--- a/src/piper/package/requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-PyYAML>=5.4.1,<6.0.0
diff --git a/src/piper/package/LICENSE b/src/travel/package/LICENSE
similarity index 100%
rename from src/piper/package/LICENSE
rename to src/travel/package/LICENSE
diff --git a/src/piper/package/MANIFEST.in b/src/travel/package/MANIFEST.in
similarity index 100%
rename from src/piper/package/MANIFEST.in
rename to src/travel/package/MANIFEST.in
diff --git a/src/travel/package/README.md b/src/travel/package/README.md
new file mode 100644
index 0000000..f2e8386
--- /dev/null
+++ b/src/travel/package/README.md
@@ -0,0 +1,29 @@
+# Travel
+
+A software manager for easy **development** and **distribution** of **Python code**.
+
+The main features that Travel adds to Python are:
+
+- Support for large-scale, **multi-package projects**
+- **Reproducibility** (clear, transparent **dependency management**)
+- Robust **development-lifecycle**, from blueprinting to distribution 
+
+Travel is inspired by what **[Maven](https://maven.apache.org/)** is for Java and uses [Pip](https://github.com/pypa/pip) and [Virtual Environments](https://docs.python.org/3/library/venv.html).
+
+## Why Travel
+
+**Python is great** in many things, particularly for **scripting**. But it is powerful enough to create **complex software** too. Still, when doing so, it lacks some robustness and quickness.
+
+Instead:
+ 
+- Travel lets you **forget about repetitive setup of Python projects**, with creation of Virtual Environments, issues with imports, PYTHONPATH, folder structures. It does all of this for you.
+
+- Travel ensures that when you run a Python script, all its **requirements are implicitly installed**. Along with reproducibility, this makes room for **easy collaboration** between developers.
+
+- Also, while with standard tools it's hard to have multiple packages, one requiring the other, with Travel is a matter of **few YAML files**. You can **split your project** in several packages instead of having tons of requirements, code replication and/or single package-monoliths. 
+
+All in all, Travel lets you design **proper structure and modularity** for your code. No worries about imports and low-level stuff.
+
+## Example of Travel Project
+
+You can find an example of Travel project [here](https://github.com/travel-tools/travel/tree/master/src/travel/tests/data/complexproject).
\ No newline at end of file
diff --git a/src/travel/package/requirements.txt b/src/travel/package/requirements.txt
new file mode 100644
index 0000000..0f40d54
--- /dev/null
+++ b/src/travel/package/requirements.txt
@@ -0,0 +1,2 @@
+PyYAML>=5.4.1,<6.0.0
+cookiecutter>=1.7.3,<2.0.0
diff --git a/src/piper/package/setup.py b/src/travel/package/setup.py
similarity index 89%
rename from src/piper/package/setup.py
rename to src/travel/package/setup.py
index f29c343..848c71e 100644
--- a/src/piper/package/setup.py
+++ b/src/travel/package/setup.py
@@ -24,24 +24,24 @@ def _get_resources(package_name):
 
 
 # Package configuration
-_NAME="piper-tools"
+_NAME="travel"
 setup(
     name=_NAME,
-    version="0.4.0",
+    version="0.0.0",
     description="A software manager for easy development and distribution of Python code",
     packages=find_packages(),
-    package_data={"piper": _get_resources("piper")},
+    package_data={"travel": _get_resources("travel")},
     install_requires=requirements,
     python_requires=">=3.7",
     entry_points={
         'console_scripts': [
-            'piper = piper.__main__:main',
+            'travel = travel.__main__:main',
         ],
     },
 
     long_description=readme,
     long_description_content_type="text/markdown",
-    url="https://github.com/piper-tools/piper",
+    url="https://github.com/travel-tools/travel",
     author="Federico Pugliese",
     author_email="federico.pugliese.wr@gmail.com",
     license="Apache Software License, Version 2.0",
diff --git a/src/piper/package/piper/__init__.py b/src/travel/package/travel/__init__.py
similarity index 100%
rename from src/piper/package/piper/__init__.py
rename to src/travel/package/travel/__init__.py
diff --git a/src/travel/package/travel/__main__.py b/src/travel/package/travel/__main__.py
new file mode 100644
index 0000000..272710e
--- /dev/null
+++ b/src/travel/package/travel/__main__.py
@@ -0,0 +1,75 @@
+import argparse
+import logging
+import os
+
+from travel.cli import python_wrapper, packer, planner, adder
+from travel.cli.cleaner import Cleaner
+from travel.cli.setupper import Setupper
+
+logger = logging.getLogger(__name__)
+
+
+def main():
+
+    # Travel
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--debug", help="Print a stacktrace of errors", action="store_true")
+    parser.add_argument("--context", default=os.getcwd(), help="Path to the folder containing the bag.yml file")
+    parser.set_defaults(action=lambda args: parser.parse_args(["-h"]))
+    subparsers = parser.add_subparsers()
+
+    # Clean
+    clean = subparsers.add_parser("clean")
+    clean.set_defaults(action=lambda args, rest: Cleaner().manage(args.context))
+
+    # Plan
+    plan = subparsers.add_parser("plan")
+    plan.add_argument("name", help="Main bag name (name of the project)")
+    plan.set_defaults(action=lambda args, rest: planner.run(args.context, args.name))
+
+    # Add
+    plan = subparsers.add_parser("add")
+    plan.add_argument("plan", help="Plan URL or path")
+    plan.add_argument("--checkout", help="Name of the branch to checkout", required=False)
+    plan.add_argument("--directory", help="Name of the directory in the plan to use", required=False)
+    plan.add_argument("--no-input", help="Do not ask for inputs", required=False, action="store_true")
+    plan.add_argument("--config", help="Config in key=value format", nargs="+", required=False)
+    plan.set_defaults(action=lambda args, rest: adder.run(args.context, args.plan, checkout=args.checkout, directory=args.directory, no_input=args.no_input, config=args.config))
+
+    # Setup
+    setup = subparsers.add_parser("setup")
+    setup.set_defaults(action=lambda args, rest: Setupper().manage(args.context))
+
+    # Pack
+    pack = subparsers.add_parser("pack")
+    pack.add_argument("--target", help="Name of the bag to run setup.py commands", required=False)
+    pack.add_argument("--no-setup", help="Do not update the venvs", required=False, action="store_true")
+    pack.set_defaults(action=lambda args, rest: packer.pack(args.context, rest, target=args.target, setup=not args.no_setup))
+
+    # Release
+
+    # # Python
+    # python = subparsers.add_parser("python")
+    # python.add_argument("package", help="Name of the bag (it will be used to activate its venv)")
+    # python.set_defaults(action=lambda args, rest: python_wrapper.run(args.context, args.package, rest))
+
+    # Parse args
+    arguments, remainder = parser.parse_known_args()
+
+    try:
+
+        # Invoke the corresponding functions
+        arguments.action(arguments, remainder)
+        logger.info("All done.")
+
+    except Exception as e:
+
+        if arguments.debug:
+            logger.exception("")
+        else:
+            logger.error(e if str(e) else f"Error: {type(e)}")
+        exit(1)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/piper/package/piper/cli/__init__.py b/src/travel/package/travel/cli/__init__.py
similarity index 100%
rename from src/piper/package/piper/cli/__init__.py
rename to src/travel/package/travel/cli/__init__.py
diff --git a/src/travel/package/travel/cli/adder.py b/src/travel/package/travel/cli/adder.py
new file mode 100644
index 0000000..664578a
--- /dev/null
+++ b/src/travel/package/travel/cli/adder.py
@@ -0,0 +1,32 @@
+import logging
+from typing import List
+
+from cookiecutter.main import cookiecutter
+
+logger = logging.getLogger(__name__)
+
+
+def _validate(config):
+    if "=" not in config:
+        raise ValueError(f"Configs should have '=', so this is not valid: {config}")
+    return config
+
+
+def run(context: str, plan: str, checkout: str = None, directory: str = None, no_input: bool = False, config: List[str] = None):
+
+    extra_context = {
+        key: value
+        for key, value in [
+            _validate(c).split("=", maxsplit=1)
+            for c in (config if config else [])
+        ]
+    }
+
+    cookiecutter(
+        template=plan,
+        output_dir=context,
+        checkout=checkout,
+        directory=directory,
+        no_input=no_input,
+        extra_context=extra_context
+    )
diff --git a/src/travel/package/travel/cli/base.py b/src/travel/package/travel/cli/base.py
new file mode 100644
index 0000000..498a154
--- /dev/null
+++ b/src/travel/package/travel/cli/base.py
@@ -0,0 +1,60 @@
+import abc
+import logging
+from typing import Set
+
+from travel.config.bag import Bag
+from travel.config.reader import get_bag_name, read_all_bags, parse_bags
+from travel.custom.tasks import performer
+from travel.tools.python import main_python, Python
+
+logger = logging.getLogger(__name__)
+
+
+class TravelCommand(abc.ABC):
+
+    def __init__(self, python: Python = main_python):
+        self.main_python = python
+
+    @abc.abstractmethod
+    def _phase_name(self) -> str:
+        pass
+
+    def _perform_tasks(self, bag: Bag, step: str):
+        return performer.perform_tasks(self._phase_name(), step, bag)
+
+    @abc.abstractmethod
+    def _manage(self, bag: Bag):
+        pass
+
+    def _manage_from_bag_recursive(self, bag: Bag, done: Set[Bag]):
+
+        if not bag.group:
+
+            # Manage dependencies first, then the bag
+            for b in [*bag.flat_dependencies(), bag]:
+                if b not in done:
+                    logger.info("")
+                    logger.info("="*60)
+                    logger.info(f"=== {b.name.center(52, ' ')} ===")
+                    logger.info("="*60)
+                    self._perform_tasks(b, "pre")
+                    self._manage(b)
+                    self._perform_tasks(b, "post")
+                    logger.info("=" * 60)
+                    logger.info("")
+                    logger.info("")
+                    done.add(b)
+
+        else:
+
+            # Manage each element of the group
+            for bag in bag.group:
+                self._manage_from_bag_recursive(bag, done=done)
+
+    def manage_from_bag(self, bag: Bag):
+        self._manage_from_bag_recursive(bag, done=set())
+
+    def manage(self, context: str, target: str = None) -> (Bag, Bag):
+        bag, bags = parse_bags(context, target)
+        self.manage_from_bag(bag)
+        return bag, bags
diff --git a/src/travel/package/travel/cli/cleaner.py b/src/travel/package/travel/cli/cleaner.py
new file mode 100644
index 0000000..635b12d
--- /dev/null
+++ b/src/travel/package/travel/cli/cleaner.py
@@ -0,0 +1,43 @@
+import logging
+import os
+import shutil
+
+from travel.cli.base import TravelCommand
+from travel.config.bag import Bag
+from travel.custom.scopes.scoped_venvs import ScopedVirtualenvs
+from travel.tools.venv import Virtualenv
+
+logger = logging.getLogger(__name__)
+
+
+class Cleaner(TravelCommand):
+
+    def _phase_name(self):
+        return "clean"
+
+    def _perform_tasks(self, bag: Bag, step: str):
+        if step == "post":
+            pass
+        else:
+            super()._perform_tasks(bag, step)
+
+    def _manage(self, bag: Bag):
+
+        logger.info(f"Cleaning {bag.name}")
+        to_remove = [
+            os.path.join(bag.setup_py_folder, f"{bag.package}.egg-info"),     # egg-info
+            Virtualenv(bag).path,                                              # venv folder
+            bag.build_folder                                                   # artifacts build folder
+        ]
+
+        # If with scopes, remove them too
+        if bag.scopes:
+            scopes = ScopedVirtualenvs(bag)
+            for env in scopes.envs.values():
+                to_remove.append(env.path)
+
+        # Clean
+        for path in to_remove:
+            if os.path.isdir(path):
+                shutil.rmtree(path)
+
diff --git a/src/piper/package/piper/cli/packer.py b/src/travel/package/travel/cli/packer.py
similarity index 55%
rename from src/piper/package/piper/cli/packer.py
rename to src/travel/package/travel/cli/packer.py
index 0d99b4c..aab2047 100644
--- a/src/piper/package/piper/cli/packer.py
+++ b/src/travel/package/travel/cli/packer.py
@@ -4,36 +4,41 @@
 from distutils.dir_util import copy_tree
 
 import setuptools
-from piper.cli.setupper import Setupper
-from piper.custom.tasks import performer
-from piper.tools.venv import Virtualenv
+from travel.cli.setupper import Setupper
+from travel.config.reader import parse_bags
+from travel.custom.tasks import performer
+from travel.tools.venv import Virtualenv
 
 logger = logging.getLogger(__name__)
 
 
-def pack(context: str, command: str, package: str = None):
+def pack(context: str, command: str, target: str = None, setup: bool = True):
 
-    # Setup the pipes and dependencies
-    current_pipe, all_pipes = Setupper().manage(context, package=package)
+    # Setup the bags and dependencies
+    if setup:
+        current_bag, all_bags = Setupper().manage(context, target=target)
+    else:
+        current_bag, all_bags = parse_bags(context, target)
+        Virtualenv(current_bag).create()
 
     # Pre-pack
-    performer.perform_tasks("pack", "pre", current_pipe)
+    performer.perform_tasks("pack", "pre", current_bag)
 
     # Get the right python (this might be useful in case the setup.py uses a particular syntax)
-    env = Virtualenv(current_pipe)
+    env = Virtualenv(current_bag)
 
     # Clean the previous target folder, if existing
-    build_folder = current_pipe.build_folder
+    build_folder = current_bag.build_folder
     if os.path.isdir(build_folder):
         shutil.rmtree(build_folder)
 
     # Copy the structure of this package
-    _copy_folder(current_pipe.setup_py_folder, build_folder)
-    source_build_folder = os.path.join(build_folder, os.path.basename(current_pipe.setup_py_folder))
+    _copy_folder(current_bag.setup_py_folder, build_folder)
+    source_build_folder = os.path.join(build_folder, os.path.basename(current_bag.setup_py_folder))
     # For all dependencies, copy their code too
-    for dep in current_pipe.flat_dependencies():
+    for dep in current_bag.flat_dependencies():
         # For all code packages, copy it inside the copied setup.py folder
-        for folder in [p for p in setuptools.find_packages(where=dep.setup_py_folder) if "." not in p]:
+        for folder in [b for b in setuptools.find_packages(where=dep.setup_py_folder) if "." not in b]:
             _copy_folder(
                 os.path.join(dep.setup_py_folder, folder),
                 os.path.join(source_build_folder)
@@ -44,7 +49,7 @@ def pack(context: str, command: str, package: str = None):
     env.python.run(f"{setup_py} {' '.join(command)}", cwd=source_build_folder)  # TODO should check for spaces and commas, or use list!
 
     # Post-pack
-    performer.perform_tasks("pack", "post", current_pipe)
+    performer.perform_tasks("pack", "post", current_bag)
 
 
 def _copy_folder(source, destination):
diff --git a/src/travel/package/travel/cli/planner.py b/src/travel/package/travel/cli/planner.py
new file mode 100644
index 0000000..f345078
--- /dev/null
+++ b/src/travel/package/travel/cli/planner.py
@@ -0,0 +1,79 @@
+import logging
+import os
+import shutil
+from pathlib import Path
+from typing import List, Dict, Any
+
+import yaml
+from cookiecutter.exceptions import OutputDirExistsException
+from cookiecutter.main import cookiecutter
+from travel.config.reader import BAG_FILE
+from travel.config.sanitizers import name_sanitizer
+
+logger = logging.getLogger(__name__)
+_PLAN = "plan"
+_TRAVEL_FILE = "travel.yml"
+_CONFIG = "config"
+_CHECKOUT = "checkout"
+_DIRECTORY = "directory"
+
+
+def _is_a_plan(properties):
+    return _PLAN in properties and isinstance(properties[_PLAN], str)
+
+
+def _generate_breath_first(folder: str, yml: Dict[str, Any]):
+
+    # Generate these plans
+    for bag, properties in yml.items():
+
+        # Get the folder where the bag will be created
+        name_sanitizer.sanitize_name(bag)
+
+        # Is it a plan?
+        if _is_a_plan(properties):
+
+            # Create it
+            plan = properties[_PLAN]
+            cookiecutter(
+                template=plan,
+                output_dir=folder,
+                no_input=True,
+                extra_context=properties[_CONFIG],
+                checkout=properties.get(_CHECKOUT),
+                directory=properties.get(_DIRECTORY)
+            )
+
+    # Create the "folder" bag.yml file
+    bag_file = os.path.join(folder, BAG_FILE)
+    if not os.path.isfile(bag_file):
+        Path(bag_file).touch()
+
+    # Generate the subbags
+    for bag, properties in yml.items():
+        bag_folder = os.path.join(folder, bag)
+        if not _is_a_plan(properties):
+            _generate_breath_first(bag_folder, properties)
+
+
+def run(context: str, name: str):
+
+    # Read the travel file
+    travel_file = os.path.join(context, _TRAVEL_FILE)
+    with open(travel_file) as f:
+        yml = yaml.load(f, Loader=yaml.SafeLoader)
+    if not yml:
+        raise ValueError("Travel file must not be empty.")
+
+    # Generate
+    folder = os.path.join(context, name)
+    try:
+        _generate_breath_first(folder, yml)
+    except OutputDirExistsException as e:
+        # If the error is just because the folder exists, exit
+        raise e
+    except Exception as e:
+        # Remove in case of any other error
+        if Path(folder).is_dir():
+            shutil.rmtree(folder)
+        raise e
diff --git a/src/piper/package/piper/cli/python_wrapper.py b/src/travel/package/travel/cli/python_wrapper.py
similarity index 57%
rename from src/piper/package/piper/cli/python_wrapper.py
rename to src/travel/package/travel/cli/python_wrapper.py
index 9641e81..02d4cd5 100644
--- a/src/piper/package/piper/cli/python_wrapper.py
+++ b/src/travel/package/travel/cli/python_wrapper.py
@@ -1,20 +1,20 @@
 import logging
 import os
 
-from piper.cli.setupper import Setupper
-from piper.tools.python import main_python
-from piper.tools.venv import Virtualenv
+from travel.cli.setupper import Setupper
+from travel.tools.python import main_python
+from travel.tools.venv import Virtualenv
 
 logger = logging.getLogger(__name__)
 
 
 def run(context: str, package: str, command: str):
 
-    # Setup the pipes and dependencies
-    current_pipe, all_pipes = Setupper().manage(context, package=package)
+    # Setup the bags and dependencies
+    current_bag, all_bags = Setupper().manage(context, package=package)
 
     # Run the code
-    env = Virtualenv(all_pipes[package])
+    env = Virtualenv(all_bags[package])
     if os.path.isdir(env.path):
         python = env.python
     else:
diff --git a/src/travel/package/travel/cli/setupper.py b/src/travel/package/travel/cli/setupper.py
new file mode 100644
index 0000000..9027dc8
--- /dev/null
+++ b/src/travel/package/travel/cli/setupper.py
@@ -0,0 +1,34 @@
+import os
+from pathlib import Path
+
+from travel.cli.base import TravelCommand
+from travel.config.bag import Bag
+from travel.custom.scopes.scoped_venvs import ScopedVirtualenvs
+from travel.tools.venv import Virtualenv
+
+
+class Setupper(TravelCommand):
+
+    def _phase_name(self) -> str:
+        return "setup"
+
+    def _manage(self, bag: Bag):
+
+        # Create the virtualenv
+        venv = Virtualenv(bag, touch_requirements_file=True)
+        venv.create()
+        updated = venv.update()
+
+        # Pip freeze
+        if updated:
+            venv.freeze()
+
+        # Create the scopes
+        if bag.scopes:
+            scopes = ScopedVirtualenvs(bag, touch_requirements_file=True)
+            for scope in bag.scopes:
+                scopes.create(scope)
+                updated = scopes.update(scope)
+                if updated:
+                    scopes.freeze(scope)
+
diff --git a/src/piper/package/piper/config/__init__.py b/src/travel/package/travel/config/__init__.py
similarity index 100%
rename from src/piper/package/piper/config/__init__.py
rename to src/travel/package/travel/config/__init__.py
diff --git a/src/piper/package/piper/config/pipe.py b/src/travel/package/travel/config/bag.py
similarity index 74%
rename from src/piper/package/piper/config/pipe.py
rename to src/travel/package/travel/config/bag.py
index fa9543f..fa0c3bc 100644
--- a/src/piper/package/piper/config/pipe.py
+++ b/src/travel/package/travel/config/bag.py
@@ -1,15 +1,15 @@
 import logging
 import os
 
-from piper.config.sanitizers import python_sanitizer, pip_sanitizer
-from piper.config.subconfigs.pip import PipConfig
-from piper.config.subconfigs.scopes import ScopeConfig
-from piper.custom.tasks.task import Task
+from travel.config.sanitizers import python_sanitizer, pip_sanitizer
+from travel.config.subconfigs.pip import PipConfig
+from travel.config.subconfigs.scopes import ScopeConfig
+from travel.custom.tasks.task import Task
 
 logger = logging.getLogger(__name__)
 
 
-class Pipe:
+class Bag:
 
     def __init__(self, location: str, yml: dict, root_context: str = None):
         self.location = location
@@ -44,21 +44,21 @@ def __init__(self, location: str, yml: dict, root_context: str = None):
 
         # If there are still configs, they are unknown. Print a warning (for retro-compatibility)
         if config:
-            logger.warning(f"Unknown configuration in pipe file \"{self.name}\": {config}")
+            logger.warning(f"Unknown configuration in bag file \"{self.name}\": {config}")
 
-    def fill_dependency_with_pipe(self, pipe):
-        self.dependencies[pipe.name] = pipe
+    def fill_dependency_with_bag(self, bag):
+        self.dependencies[bag.name] = bag
 
     def flat_dependencies(self, with_current: bool = False):
 
-        def visit(pipe, visited, level=0):
-            for dep in pipe.dependencies.values():
+        def visit(bag, visited, level=0):
+            for dep in bag.dependencies.values():
                 visited[dep] = max(level, visited.get(dep, level))
                 visit(dep, visited, level=level+1)
             return visited
 
-        pipes = visit(self, {})
-        dependencies = [pipe for pipe, level in sorted(pipes.items(), key=lambda x: x[1], reverse=True)]
+        bags = visit(self, {})
+        dependencies = [bag for bag, level in sorted(bags.items(), key=lambda x: x[1], reverse=True)]
         if with_current:
             dependencies.append(self)
         return dependencies
diff --git a/src/travel/package/travel/config/reader.py b/src/travel/package/travel/config/reader.py
new file mode 100644
index 0000000..596a27b
--- /dev/null
+++ b/src/travel/package/travel/config/reader.py
@@ -0,0 +1,84 @@
+import logging
+import os
+from pathlib import Path
+from typing import List, Dict
+
+import yaml
+from travel.config.bag import Bag
+
+BAG_FILE = "bag.yml"
+
+
+logger = logging.getLogger(__name__)
+
+
+def _read_bag(location: str) -> Bag:
+
+    # Read the bag file
+    path = os.path.join(location, BAG_FILE)
+    with open(path) as f:
+        yml = yaml.load(f, Loader=yaml.SafeLoader) or {}
+        return Bag(location=location, yml=yml)
+
+
+def _has_bag(location: str) -> bool:
+    return os.path.isfile(os.path.join(location, BAG_FILE))
+
+
+def get_bag_name(location: str) -> str:
+    return os.path.basename(os.path.normpath(location))
+
+
+def read_all_bags(location: str) -> Dict[str, Bag]:
+
+    # Find the uppermost (parent) bag file
+    uppermost = location
+    while _has_bag(location):
+        uppermost = location
+        location = str(Path(location).parent)
+
+    # Read main bag file and baged bag files
+    bags = {bag.name: bag for bag in _read_bags_from(uppermost)}
+    # Set the root context
+    for b in bags.values():
+        b.root_context = uppermost
+
+    # Build dependencies
+    for name in bags:
+        for bag in bags.values():
+            if name in bag.dependencies:
+                bag.fill_dependency_with_bag(bags[name])
+    return bags
+
+
+def parse_bags(location: str, target: str = None) -> (Bag, Bag):
+
+    # Read the target bag and all the bags
+    target = target or get_bag_name(location)
+    bags = read_all_bags(location)
+
+    # Manage this target bag
+    if target not in bags:
+        raise ValueError(f"The specified bag \"{target}\" does not exist in {location}")
+    bag = bags[target]
+    return bag, bags
+
+
+def _read_bags_from(location: str) -> List[Bag]:
+
+    # Read the local bag
+    group = []
+    bag = _read_bag(location)
+
+    # Read all bags recursively
+    bags = [bag]
+    for directory in os.listdir(location):
+        current = os.path.join(location, directory)
+        if _has_bag(current):
+            children = _read_bags_from(current)
+            group = group + children
+            bags = bags + children
+
+    # Set the baged group
+    bag.group = group
+    return bags
diff --git a/src/piper/package/piper/config/sanitizers/__init__.py b/src/travel/package/travel/config/sanitizers/__init__.py
similarity index 100%
rename from src/piper/package/piper/config/sanitizers/__init__.py
rename to src/travel/package/travel/config/sanitizers/__init__.py
diff --git a/src/piper/package/piper/config/sanitizers/asserter.py b/src/travel/package/travel/config/sanitizers/asserter.py
similarity index 100%
rename from src/piper/package/piper/config/sanitizers/asserter.py
rename to src/travel/package/travel/config/sanitizers/asserter.py
diff --git a/src/piper/package/piper/config/sanitizers/name_sanitizer.py b/src/travel/package/travel/config/sanitizers/name_sanitizer.py
similarity index 70%
rename from src/piper/package/piper/config/sanitizers/name_sanitizer.py
rename to src/travel/package/travel/config/sanitizers/name_sanitizer.py
index 6d79bb5..6506bf0 100644
--- a/src/piper/package/piper/config/sanitizers/name_sanitizer.py
+++ b/src/travel/package/travel/config/sanitizers/name_sanitizer.py
@@ -1,6 +1,6 @@
 import re
 
-from piper.config.sanitizers import asserter
+from travel.config.sanitizers import asserter
 
 _NAME = re.compile(r"^[A-Za-z0-9\-\._]+$")
 
diff --git a/src/piper/package/piper/config/sanitizers/pip_sanitizer.py b/src/travel/package/travel/config/sanitizers/pip_sanitizer.py
similarity index 93%
rename from src/piper/package/piper/config/sanitizers/pip_sanitizer.py
rename to src/travel/package/travel/config/sanitizers/pip_sanitizer.py
index 0db7315..0d4d091 100644
--- a/src/piper/package/piper/config/sanitizers/pip_sanitizer.py
+++ b/src/travel/package/travel/config/sanitizers/pip_sanitizer.py
@@ -2,7 +2,7 @@
 import re
 from typing import Optional
 
-from piper.config.sanitizers import asserter
+from travel.config.sanitizers import asserter
 
 
 LATEST_PIP = "latest"
diff --git a/src/piper/package/piper/config/sanitizers/python_sanitizer.py b/src/travel/package/travel/config/sanitizers/python_sanitizer.py
similarity index 81%
rename from src/piper/package/piper/config/sanitizers/python_sanitizer.py
rename to src/travel/package/travel/config/sanitizers/python_sanitizer.py
index 8fbf914..ad97816 100644
--- a/src/piper/package/piper/config/sanitizers/python_sanitizer.py
+++ b/src/travel/package/travel/config/sanitizers/python_sanitizer.py
@@ -1,7 +1,7 @@
 import re
 from typing import Optional
 
-from piper.config.sanitizers import asserter
+from travel.config.sanitizers import asserter
 
 _VERSION = re.compile(r"^\d+\.\d+.\d+$")
 
diff --git a/src/piper/package/piper/config/subconfigs/__init__.py b/src/travel/package/travel/config/subconfigs/__init__.py
similarity index 100%
rename from src/piper/package/piper/config/subconfigs/__init__.py
rename to src/travel/package/travel/config/subconfigs/__init__.py
diff --git a/src/piper/package/piper/config/subconfigs/pip.py b/src/travel/package/travel/config/subconfigs/pip.py
similarity index 85%
rename from src/piper/package/piper/config/subconfigs/pip.py
rename to src/travel/package/travel/config/subconfigs/pip.py
index 67fcae0..ac4ce07 100644
--- a/src/piper/package/piper/config/subconfigs/pip.py
+++ b/src/travel/package/travel/config/subconfigs/pip.py
@@ -1,6 +1,6 @@
 import logging
 
-from piper.config.sanitizers import pip_sanitizer
+from travel.config.sanitizers import pip_sanitizer
 
 logger = logging.getLogger(__name__)
 
diff --git a/src/piper/package/piper/config/subconfigs/scopes.py b/src/travel/package/travel/config/subconfigs/scopes.py
similarity index 86%
rename from src/piper/package/piper/config/subconfigs/scopes.py
rename to src/travel/package/travel/config/subconfigs/scopes.py
index 143689e..3a1531b 100644
--- a/src/piper/package/piper/config/subconfigs/scopes.py
+++ b/src/travel/package/travel/config/subconfigs/scopes.py
@@ -1,6 +1,6 @@
 import logging
 
-from piper.config.sanitizers import pip_sanitizer
+from travel.config.sanitizers import pip_sanitizer
 
 
 logger = logging.getLogger(__name__)
diff --git a/src/piper/package/piper/custom/__init__.py b/src/travel/package/travel/custom/__init__.py
similarity index 100%
rename from src/piper/package/piper/custom/__init__.py
rename to src/travel/package/travel/custom/__init__.py
diff --git a/src/piper/package/piper/custom/scopes/__init__.py b/src/travel/package/travel/custom/scopes/__init__.py
similarity index 100%
rename from src/piper/package/piper/custom/scopes/__init__.py
rename to src/travel/package/travel/custom/scopes/__init__.py
diff --git a/src/travel/package/travel/custom/scopes/scoped_venvs.py b/src/travel/package/travel/custom/scopes/scoped_venvs.py
new file mode 100644
index 0000000..d4eadc5
--- /dev/null
+++ b/src/travel/package/travel/custom/scopes/scoped_venvs.py
@@ -0,0 +1,45 @@
+import os
+
+from travel.config.bag import Bag
+from travel.tools.base_venv import BaseVirtualenv
+from travel.tools.python import Python
+from travel.tools.python import main_python as default_python
+
+
+class ScopedVirtualenvs():
+
+    def __init__(self, bag: Bag, main_python: Python = default_python, touch_requirements_file: bool = False):
+        self.envs = {
+            scope: BaseVirtualenv(
+                location=bag.location,
+                name_suffix=f"{bag.name}-{scope}",
+                pip_config=bag.pip,
+                dependencies=bag.flat_dependencies(with_current=True),
+                requirements_file=os.path.join(bag.location, f"requirements_{scope}.txt"),
+                main_python=main_python,
+                touch_requirements_file=touch_requirements_file,
+                extra_requirements=config.requirements
+            )
+            for scope, config in bag.scopes.items()
+        }
+
+    def create(self, scope: str):
+        self.envs[scope].create()
+
+    def create_all(self):
+        for scope in self.envs.keys():
+            self.create(scope)
+
+    def update(self, scope: str) -> bool:
+        return self.envs[scope].update()
+
+    def update_all(self):
+        for scope in self.envs.keys():
+            self.update(scope)
+
+    def freeze(self, scope: str):
+        self.envs[scope].freeze()
+
+    def freeze_all(self):
+        for scope in self.envs.keys():
+            self.freeze(scope)
diff --git a/src/piper/package/piper/custom/tasks/__init__.py b/src/travel/package/travel/custom/tasks/__init__.py
similarity index 100%
rename from src/piper/package/piper/custom/tasks/__init__.py
rename to src/travel/package/travel/custom/tasks/__init__.py
diff --git a/src/piper/package/piper/custom/tasks/performer.py b/src/travel/package/travel/custom/tasks/performer.py
similarity index 56%
rename from src/piper/package/piper/custom/tasks/performer.py
rename to src/travel/package/travel/custom/tasks/performer.py
index 2236e8d..3681134 100644
--- a/src/piper/package/piper/custom/tasks/performer.py
+++ b/src/travel/package/travel/custom/tasks/performer.py
@@ -2,10 +2,10 @@
 import os
 
 import pkg_resources
-from piper import RESOURCES_LOCATION
-from piper.config.pipe import Pipe
-from piper.config.sanitizers import pip_sanitizer
-from piper.tools.venv import Virtualenv
+from travel import RESOURCES_LOCATION
+from travel.config.bag import Bag
+from travel.config.sanitizers import pip_sanitizer
+from travel.tools.venv import Virtualenv
 
 _PERFORM_TASK = os.path.join(RESOURCES_LOCATION, "tasks", "perform.py")
 
@@ -13,28 +13,28 @@
 logger = logging.getLogger(__name__)
 
 
-def perform_tasks(phase: str, step: str, pipe: Pipe):
+def perform_tasks(phase: str, step: str, bag: Bag):
 
     # Get tasks
-    tasks = pipe.tasks.get(phase, {}).get(step, {})
+    tasks = bag.tasks.get(phase, {}).get(step, {})
     if tasks:
 
         # Create the env for tasks # TODO could be different venvs to avoid conflicts?
         logger.info("*"*60)
         logger.info(f"   TASKS {phase}: {step}   ".center(60, "*"))
         logger.info("*" * 60)
-        venv = Virtualenv(Pipe(location=pipe.tasks_folder, yml={}))
+        venv = Virtualenv(Bag(location=bag.tasks_folder, yml={}))
         venv.create()
 
         # Install PyYAML for the performer
         version = pip_sanitizer.sanitize_version(pkg_resources.get_distribution("PyYAML").version)
         venv.pip.install(f"PyYAML=={version}")
 
-        # Install the pipertasks
-        venv.pip.install([t.pipertask for t in tasks], allow_pipes_from=pipe.root_context)
+        # Install the traveltasks
+        venv.pip.install([t.pipertask for t in tasks], allow_bags_from=bag.root_context)
 
         # Perform each task in order
         for task in tasks:
-            venv.python.run(f"{_PERFORM_TASK} --context {pipe.location} --task {task.name}")
+            venv.python.run(f"{_PERFORM_TASK} --context {bag.location} --task {task.name}")
         logger.info(f"   END TASKS {phase}: {step}   ".center(60, "*"))
         logger.info("*"*60)
diff --git a/src/piper/package/piper/custom/tasks/task.py b/src/travel/package/travel/custom/tasks/task.py
similarity index 54%
rename from src/piper/package/piper/custom/tasks/task.py
rename to src/travel/package/travel/custom/tasks/task.py
index add8f8b..368731b 100644
--- a/src/piper/package/piper/custom/tasks/task.py
+++ b/src/travel/package/travel/custom/tasks/task.py
@@ -1,5 +1,5 @@
-from piper.config.sanitizers.name_sanitizer import sanitize_name
-from piper.config.sanitizers.pip_sanitizer import sanitize_versioned_package
+from travel.config.sanitizers.name_sanitizer import sanitize_name
+from travel.config.sanitizers.pip_sanitizer import sanitize_versioned_package
 
 
 class Task:
@@ -11,4 +11,4 @@ def __init__(self, yml):
 
         # If there are still configs, they are unknown. Raise an error
         if yml:
-            raise KeyError(f"Unknown configuration in pipe file, task {self.name}: {yml}")
+            raise KeyError(f"Unknown configuration in bag file, task {self.name}: {yml}")
diff --git a/src/piper/package/piper/resources/tasks/perform.py b/src/travel/package/travel/resources/tasks/perform.py
similarity index 85%
rename from src/piper/package/piper/resources/tasks/perform.py
rename to src/travel/package/travel/resources/tasks/perform.py
index 18e31a4..583d685 100644
--- a/src/piper/package/piper/resources/tasks/perform.py
+++ b/src/travel/package/travel/resources/tasks/perform.py
@@ -11,14 +11,14 @@
 
 
 def _read_config(context: str, name: str) -> (Dict[str, Any], str):
-    # Read the pipe file
-    with open(os.path.join(context, "pipe.yml")) as p:
-        pipe = yaml.load(p, Loader=yaml.SafeLoader) or {}
+    # Read the bag file
+    with open(os.path.join(context, "bag.yml")) as b:
+        bag = yaml.load(b, Loader=yaml.SafeLoader) or {}
 
     # Get all the tasks with matching name
     matching = [
         task
-        for step in pipe.get("tasks", {}).values()
+        for step in bag.get("tasks", {}).values()
         for tasks in step.values()
         for task in tasks
         if task.get("name") == name
diff --git a/src/piper/package/piper/tools/__init__.py b/src/travel/package/travel/tools/__init__.py
similarity index 100%
rename from src/piper/package/piper/tools/__init__.py
rename to src/travel/package/travel/tools/__init__.py
diff --git a/src/piper/package/piper/tools/base_venv.py b/src/travel/package/travel/tools/base_venv.py
similarity index 65%
rename from src/piper/package/piper/tools/base_venv.py
rename to src/travel/package/travel/tools/base_venv.py
index fffb0c2..f83abc8 100644
--- a/src/piper/package/piper/tools/base_venv.py
+++ b/src/travel/package/travel/tools/base_venv.py
@@ -5,24 +5,26 @@
 from pathlib import Path
 from typing import List
 
-from piper.config.pipe import Pipe
-from piper.config.sanitizers import pip_sanitizer
-from piper.config.sanitizers.pip_sanitizer import LATEST_PIP
-from piper.config.subconfigs.pip import PipConfig
-from piper.tools.pip import Pip
-from piper.tools.python import Python
-from piper.tools.python import main_python as default_python
-from piper.tools.shell import Shell
+from travel.config.bag import Bag
+from travel.config.sanitizers import pip_sanitizer
+from travel.config.sanitizers.pip_sanitizer import LATEST_PIP
+from travel.config.subconfigs.pip import PipConfig
+from travel.tools.outputs.latest_updates import LatestUpdate
+from travel.tools.pip import Pip
+from travel.tools.python import Python
+from travel.tools.python import main_python as default_python
+from travel.tools.shell import Shell
 
 logger = logging.getLogger(__name__)
 
 
 _CREATE_VENV = "{python} -m venv {venv}"
+LATEST_UPDATE = "latest_update.yml"
 
 
 class BaseVirtualenv:
 
-    def __init__(self, location: str, name_suffix: str, pip_config: PipConfig, dependencies: List[Pipe], requirements_file: str, main_python: Python = default_python, touch_requirements_file: bool = False, extra_requirements: List[str] = None):
+    def __init__(self, location: str, name_suffix: str, pip_config: PipConfig, dependencies: List[Bag], requirements_file: str, main_python: Python = default_python, touch_requirements_file: bool = False, extra_requirements: List[str] = None):
         self._main_python = main_python
         self._name = f"venv-{name_suffix}"
 
@@ -31,6 +33,7 @@ def __init__(self, location: str, name_suffix: str, pip_config: PipConfig, depen
         self.requirements_file = requirements_file
         self.extra_requirements = extra_requirements
 
+        self.location = location
         self.path = os.path.join(location, self._name)
         self.python = Python(path=os.path.join(self.path, "bin" if os.name == "posix" else "Scripts", "python" if os.name == "posix" else "python.exe"))
         self.pip = Pip(self.python)
@@ -54,7 +57,7 @@ def create(self) -> None:
                 )
             )
 
-    def update(self):
+    def update(self) -> bool:
 
         # Upgrade pip if required
         pip_version = self.pip_config.version
@@ -65,22 +68,42 @@ def update(self):
             else:
                 self.pip.install(f"pip=={pip_version}")
 
-        # Install requirements
-        logger.info("Installing requirements...")
-        explicit_requirements = []
-        for pipe in self.dependencies:
+        # Compute current requirements
+        dependency_names = [bag.name for bag in self.dependencies]
+        explicit_requirements = [req for bag in self.dependencies for req in bag.requirements]
+        if self.extra_requirements:
+            explicit_requirements = explicit_requirements + self.extra_requirements
 
-            # Install the pipe's package
-            self.pip.run(f"install -e {pipe.setup_py_folder}")
+        # If there is already a requirements file and a latest_update file, check: we might skip the update!
+        latest_update_path = Path(self.path)/LATEST_UPDATE
+        if Path(self.requirements_file).is_file() and latest_update_path.is_file():
+
+            # Get the freezed requirements and the written requirements
+            freezed_requirements = self._freeze()
+            with open(self.requirements_file, "r") as f:
+                written_requirements = f.read().splitlines()
+            
+            # Read the latest_update
+            latest_update = LatestUpdate.read(latest_update_path)
+
+            # If there is nothing to update, exit!
+            if (set(freezed_requirements) == set(written_requirements)
+                    and set(dependency_names) == set(latest_update.dependencies)
+                    and set(explicit_requirements) == set(latest_update.requirements)):
+                logger.info("Virtualenv is already up-to-date!")
+                return False
 
+        # Install requirements
+        logger.info("Installing requirements...")
+        for bag in self.dependencies:
+            # Install the bag's package
+            self.pip.run(f"install -e {bag.setup_py_folder}")
             # Install the explicit requirements
-            if pipe.requirements:
-                self.pip.install(pipe.requirements)
-                explicit_requirements = explicit_requirements + pipe.requirements
+            if bag.requirements:
+                self.pip.install(bag.requirements)
 
         if self.extra_requirements:
             self.pip.install(self.extra_requirements)
-            explicit_requirements = explicit_requirements + self.extra_requirements
 
         # Uninstall the unnecessary requirements
         installed_requirements = [pip_sanitizer.get_package_name(f) for f in self._freeze()]
@@ -91,7 +114,14 @@ def update(self):
             logger.warning(f"Unnecessary requirements found, they will be uninstalled: {to_uninstall}")
             self.pip.run(f"uninstall -y {to_uninstall}")
 
+        # Save the latest update
+        LatestUpdate.create(
+            requirements=explicit_requirements,
+            dependencies=dependency_names
+        ).write(latest_update_path)
+
         logger.info("Done.")
+        return True
 
     def _freeze(self) -> List[str]:
 
diff --git a/src/piper/package/piper/tools/executable.py b/src/travel/package/travel/tools/executable.py
similarity index 89%
rename from src/piper/package/piper/tools/executable.py
rename to src/travel/package/travel/tools/executable.py
index c4a34d1..348f989 100644
--- a/src/piper/package/piper/tools/executable.py
+++ b/src/travel/package/travel/tools/executable.py
@@ -1,7 +1,7 @@
 import logging
 import subprocess
 
-from piper.tools.shell import Shell
+from travel.tools.shell import Shell
 
 logger = logging.getLogger(__name__)
 
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/packagename/__init__.py b/src/travel/package/travel/tools/outputs/__init__.py
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/packagename/__init__.py
rename to src/travel/package/travel/tools/outputs/__init__.py
diff --git a/src/travel/package/travel/tools/pip.py b/src/travel/package/travel/tools/pip.py
new file mode 100644
index 0000000..185fa3d
--- /dev/null
+++ b/src/travel/package/travel/tools/pip.py
@@ -0,0 +1,41 @@
+from typing import List, Union
+
+from travel.config.reader import read_all_bags
+from travel.config.sanitizers.pip_sanitizer import sanitize_package, sanitize_versioned_package, is_just_package
+from travel.tools.executable import Executable
+from travel.tools.python import Python
+
+
+class Pip(Executable):
+
+    def __init__(self, python: Python, pre_command: str = None):
+        super().__init__(f"{python.path} -m pip --disable-pip-version-check", pre_command=pre_command)
+
+    def install(self, requirements: Union[List[str], str], allow_bags_from: Union[str, List[str]] = None):
+
+        if isinstance(requirements, str):
+            requirements = [requirements]
+
+        # Install real requirements
+        packages = [
+            f'{sanitize_versioned_package(req)}'
+            for req in requirements
+            if not is_just_package(req)
+        ]
+        if packages:
+            super().run(f"install {' '.join(packages)}")
+
+        # Install bags
+        bags = [
+            f'{sanitize_package(req)}'
+            for req in requirements
+            if is_just_package(req)
+        ]
+        if bags:
+            if not allow_bags_from:
+                raise ValueError(f"{bags} without versions (or bags) are not allowed here")
+            else:
+                allow_bags_from = allow_bags_from if isinstance(allow_bags_from, list) else [allow_bags_from]
+                for context in allow_bags_from:
+                    all_bags = read_all_bags(context)
+                    super().run(f"install -e {' '.join([b.setup_py_folder for b in all_bags.values() if b.name in bags])}")
diff --git a/src/piper/package/piper/tools/python.py b/src/travel/package/travel/tools/python.py
similarity index 88%
rename from src/piper/package/piper/tools/python.py
rename to src/travel/package/travel/tools/python.py
index 8ab4671..24e4dc6 100644
--- a/src/piper/package/piper/tools/python.py
+++ b/src/travel/package/travel/tools/python.py
@@ -2,7 +2,7 @@
 import sys
 from typing import List
 
-from piper.tools.executable import Executable
+from travel.tools.executable import Executable
 
 
 class Python(Executable):
diff --git a/src/piper/package/piper/tools/shell.py b/src/travel/package/travel/tools/shell.py
similarity index 90%
rename from src/piper/package/piper/tools/shell.py
rename to src/travel/package/travel/tools/shell.py
index e6be843..5a88e52 100644
--- a/src/piper/package/piper/tools/shell.py
+++ b/src/travel/package/travel/tools/shell.py
@@ -36,7 +36,7 @@ def live_run(self, command: str, text: bool = True, cwd: str = None):
 
         output = Queue()
 
-        def pipe(std, stream):
+        def bag(std, stream):
             # Blocking for loop: the stream will be empty only on process termination
             for line in stream:
                 output.put((std, line))
@@ -55,9 +55,9 @@ def pipe(std, stream):
         )
 
         # Start the producers
-        stdout = Thread(target=pipe, args=(_STDOUT, process.stdout))
+        stdout = Thread(target=bag, args=(_STDOUT, process.stdout))
         stdout.start()
-        stderr = Thread(target=pipe, args=(_STDERR, process.stderr))
+        stderr = Thread(target=bag, args=(_STDERR, process.stderr))
         stderr.start()
 
         # Be the consumer
diff --git a/src/travel/package/travel/tools/venv.py b/src/travel/package/travel/tools/venv.py
new file mode 100644
index 0000000..1ba73bb
--- /dev/null
+++ b/src/travel/package/travel/tools/venv.py
@@ -0,0 +1,18 @@
+from travel.config.bag import Bag
+from travel.tools.base_venv import BaseVirtualenv
+from travel.tools.python import Python
+from travel.tools.python import main_python as default_python
+
+
+class Virtualenv(BaseVirtualenv):
+
+    def __init__(self, bag: Bag, main_python: Python = default_python, touch_requirements_file: bool = False):
+        super().__init__(
+            location=bag.location,
+            name_suffix=bag.name,
+            pip_config=bag.pip,
+            dependencies=bag.flat_dependencies(with_current=True),
+            requirements_file=bag.requirements_file,
+            main_python=main_python,
+            touch_requirements_file=touch_requirements_file
+        )


Test patch:
diff --git a/devops/piper/test.bat b/devops/piper/test.bat
deleted file mode 100644
index 0ac255e..0000000
--- a/devops/piper/test.bat
+++ /dev/null
@@ -1,19 +0,0 @@
-@echo off
-
-
-cd src\piper\tests\
-pytest
-
-cd data\blueprint\complex
-piper blueprint --local-blueprints ..\locals
-cd ..\..\
-
-cd complexproject
-piper clean
-piper setup
-cd microservices\second
-venv-second\Scripts\python package\second\__init__.py
-
-piper pack sdist
-
-piper clean
diff --git a/devops/piper/test.sh b/devops/piper/test.sh
deleted file mode 100644
index 654fcc2..0000000
--- a/devops/piper/test.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-set -e
-
-cd src/piper/tests/
-pytest
-
-cd data/blueprint/complex
-piper blueprint --local-blueprints ../locals
-cd ../../
-
-cd complexproject
-piper clean
-piper setup
-cd microservices/second/
-venv-second/bin/python package/second/__init__.py
-
-piper pack sdist
-
-piper clean
diff --git a/devops/travel/test.bat b/devops/travel/test.bat
new file mode 100644
index 0000000..9d8d27f
--- /dev/null
+++ b/devops/travel/test.bat
@@ -0,0 +1,26 @@
+@echo off
+
+
+cd src\travel\tests\
+pytest
+
+cd data\plan\complex
+travel plan test
+travel add gh:travel-tools/cookiecutter-travelplan --no-input
+cd ..\..\
+
+cd complexproject
+travel clean
+travel setup
+cd microservices\second
+venv-second\Scripts\python -m second
+
+travel pack sdist
+
+travel clean
+
+travel pack --no-setup sdist
+( venv-second\Scripts\python -m second && exit 1 ) || echo No setup ok
+venv-second\Scripts\python -m pip install build\package\dist\second-0.0.0.tar.gz
+venv-second\Scripts\python -m second
+travel clean
diff --git a/devops/travel/test.sh b/devops/travel/test.sh
new file mode 100644
index 0000000..2c0ab89
--- /dev/null
+++ b/devops/travel/test.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -e
+
+cd src/travel/tests/
+pytest
+
+cd data/plan/complex
+travel plan test
+travel add gh:travel-tools/cookiecutter-travelplan --no-input
+cd ../../
+
+cd complexproject
+travel clean
+travel setup
+cd microservices/second/
+venv-second/bin/python -m second
+
+travel pack sdist
+
+travel clean
+
+travel pack --no-setup sdist
+( venv-second/bin/python -m second && exit 1 ) || echo No setup ok
+venv-second/bin/python -m pip install build/package/dist/second-0.0.0.tar.gz
+venv-second/bin/python -m second
+travel clean
diff --git a/src/piper/tests/data/blueprint/complex/blueprint.yml b/src/piper/tests/data/blueprint/complex/blueprint.yml
deleted file mode 100644
index 59b1cf3..0000000
--- a/src/piper/tests/data/blueprint/complex/blueprint.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-pipes:
-
-  common:
-    blueprint: piperblueexample
-    config:
-      package:
-        name: common
-        description: bla
-      python: 3.7.4
-
-  microservices:
-    pipes:
-
-      first:
-        blueprint: piperblueexample
-        config:
-          package:
-            name: first
-            description: bla
-          python: 3.7.4
-
-      second:
-        blueprint: piperblueexample
-        config:
-          package:
-            name: second
-            description: bla
-          python: 3.7.4
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/blueprint.py b/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/blueprint.py
deleted file mode 100644
index 729d4dc..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/blueprint.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import os
-from typing import Dict, Any
-
-from piperblue.blueprint import PiperBlueprint
-
-
-class PiperBlueprintExample(PiperBlueprint):
-
-    def get_file_and_folders_renamings(self, context: str, config: Dict[str, Any]) -> Dict[str, str]:
-
-        # Parse the config
-        package_name = config["package"]["name"]
-        package_folder = os.path.join(context, "package")
-        return {
-            os.path.join(package_folder, "packagename"): os.path.join(package_folder, package_name)
-        }
-
-    def get_substitutions_in_files(self, config: Dict[str, Any]) -> Dict[str, str]:
-
-        # Parse the config
-        python = config["python"]
-        package = config["package"]
-        package_name = package["name"]
-        description = package["description"]
-        return {
-            "package_name": package_name,
-            "description": description,
-            "python_version": python
-        }
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/setup.py b/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/setup.py
deleted file mode 100644
index 0630d8b..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/setup.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from setuptools import setup, find_packages
-import os
-
-
-# Read requirements
-requirements_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.txt")
-with open(requirements_file, "r") as f:
-    requirements = f.read().splitlines()
-
-
-# Package configuration
-_NAME = "{{package_name}}"
-setup(
-    name=_NAME,
-    version="0.0.0",
-    description="{{description}}",
-    packages=find_packages(),
-    install_requires=requirements
-)
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/pipe.yml b/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/pipe.yml
deleted file mode 100644
index d08d7c2..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/pipe.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-python: {{python_version}}
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/requirements.txt b/src/piper/tests/data/blueprint/locals/piperblueexample/package/requirements.txt
deleted file mode 100644
index e693ff9..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/package/requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-piperblue==0.1.0
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/setup.py b/src/piper/tests/data/blueprint/locals/piperblueexample/package/setup.py
deleted file mode 100644
index 689320f..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/package/setup.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from setuptools import setup, find_packages
-import os
-
-
-# Function to retrieve resources files
-def _get_resources(package_name):
-    # Get all the resources (also on nested levels)
-    res_paths = os.path.join(package_name, "resources")
-    all_resources = [os.path.join(folder, file) for folder, _, files in os.walk(res_paths) for file in files]
-    # Remove the prefix: start just from "resources"
-    return [resource[resource.index("resources"):] for resource in all_resources]
-
-
-# Read requirements
-this_location = os.path.dirname(os.path.realpath(__file__))
-requirements_file = os.path.join(this_location, "requirements.txt")
-with open(requirements_file, "r") as f:
-    requirements = f.read().splitlines()
-
-
-# Package configuration
-setup(
-    name="piperblueexample",
-    version="0.0.0",
-    description="An example of Piper Blueprint",
-    packages=find_packages(),
-    package_data={"piperblueexample": _get_resources("piperblueexample")},
-    install_requires=requirements
-)
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/pipe.yml b/src/piper/tests/data/blueprint/locals/piperblueexample/pipe.yml
deleted file mode 100644
index f70e86a..0000000
--- a/src/piper/tests/data/blueprint/locals/piperblueexample/pipe.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-python: 3.7.4
-
-requirements:
-  - piperblue==0.1.0
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/LICENSE b/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/LICENSE
deleted file mode 100644
index 8f28074..0000000
--- a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2021 Federico Pugliese
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/MANIFEST.in b/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/MANIFEST.in
deleted file mode 100644
index 5ee0522..0000000
--- a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/MANIFEST.in
+++ /dev/null
@@ -1,1 +0,0 @@
-include requirements.txt
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/pipertaskexample/__init__.py b/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/pipertaskexample/__init__.py
deleted file mode 100644
index 53231a2..0000000
--- a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/pipertaskexample/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import os
-
-PACKAGE_LOCATION = os.path.join(os.path.dirname(os.path.realpath(__file__)))
-RESOURCES_LOCATION = os.path.join(PACKAGE_LOCATION, "resources")
diff --git a/src/piper/tests/data/simpleproject/package/MANIFEST.in b/src/piper/tests/data/simpleproject/package/MANIFEST.in
deleted file mode 100644
index 5ee0522..0000000
--- a/src/piper/tests/data/simpleproject/package/MANIFEST.in
+++ /dev/null
@@ -1,1 +0,0 @@
-include requirements.txt
diff --git a/src/piper/tests/src/blueprinter/test_blueprinter.py b/src/piper/tests/src/blueprinter/test_blueprinter.py
deleted file mode 100644
index 41fd834..0000000
--- a/src/piper/tests/src/blueprinter/test_blueprinter.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import os
-import shutil
-from pathlib import Path
-from typing import Dict, Any
-
-import yaml
-from piper.cli import blueprinter
-from piper.config.reader import PIPE_FILE
-
-
-def test_blueprint(data_location):
-
-    # Folders and files
-    blueprint_folder = Path(data_location)/"blueprint"
-    blueprint_file_location = blueprint_folder/"complex"
-    local_blueprints = blueprint_folder/"locals"
-    blueprint_file = blueprint_file_location/"blueprint.yml"
-
-    # Create the pipes
-    blueprinter.run(blueprint_file_location, local_blueprints=local_blueprints)
-
-    # Check the pipes structure against the blueprint file
-    with open(blueprint_file) as f:
-        yml = yaml.load(f, Loader=yaml.SafeLoader)
-    _is_created(blueprint_file_location, yml["pipes"])
-
-    # Cleanup
-    tmp_blueprint_file = blueprint_folder/"blueprint.yml"
-    os.rename(blueprint_file, tmp_blueprint_file)
-    shutil.rmtree(blueprint_file_location)
-    os.makedirs(blueprint_file_location)
-    os.rename(tmp_blueprint_file, blueprint_file)
-
-
-def _is_created(context: Path, yml: Dict[str, Any]):
-
-    assert context.is_dir()
-    assert (context/PIPE_FILE).is_file()
-
-    for pipe, properties in yml.items():
-        if "pipes" in properties:
-            _is_created(context/pipe, properties["pipes"])
diff --git a/src/piper/tests/src/pipefiles/test_pipe_file_reader.py b/src/piper/tests/src/pipefiles/test_pipe_file_reader.py
deleted file mode 100644
index ccf9e1c..0000000
--- a/src/piper/tests/src/pipefiles/test_pipe_file_reader.py
+++ /dev/null
@@ -1,34 +0,0 @@
-def test_finder(complex_project):
-    # Number of pipes
-    pipes = complex_project.pipes
-    assert len(pipes) == 7
-    assert len([
-        p for p in pipes.keys() 
-        if p in [
-            complex_project.name, 
-            complex_project.common, 
-            complex_project.tasks, 
-            complex_project.microservices, 
-            complex_project.first, 
-            complex_project.second, 
-            complex_project.pipertask_example
-        ]
-    ]) == 7
-
-
-def test_structure(complex_project):
-    pipes = complex_project.pipes
-    # Root context
-    assert len([p for p in pipes.values() if p.root_context != complex_project.piper_project]) == 0
-    # Dependencies
-    assert pipes[complex_project.second].flat_dependencies(with_current=True) == [
-        pipes[complex_project.common],
-        pipes[complex_project.first],
-        pipes[complex_project.second]
-    ]
-
-
-def test_read_tasks(complex_project):
-    # Tasks reader
-    pipes = complex_project.pipes
-    assert len(pipes[complex_project.first].tasks["setup"]["pre"]) == 2
diff --git a/src/piper/tests/src/requirements/test_venv.py b/src/piper/tests/src/requirements/test_venv.py
deleted file mode 100644
index 106e54f..0000000
--- a/src/piper/tests/src/requirements/test_venv.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import os
-
-from piper.cli.cleaner import Cleaner
-from piper.config.sanitizers.pip_sanitizer import LATEST_PIP
-from piper.tools.venv import Virtualenv
-
-
-def test_pip_version(complex_project):
-
-    # Pipe
-    pipe = complex_project.pipes[complex_project.common]
-
-    # Create the virtualenv
-    venv = Virtualenv(pipe)
-    venv.create()
-    venv.update()
-
-    # Check pip version
-    assert pipe.pip.version in venv.pip.run("--version", capture=True).stdout
-
-    # Force pip version to latest
-    old_pip_version = pipe.pip.version
-    pipe.pip.version = LATEST_PIP
-
-    # Update pip and check if the old version is no more there
-    venv.update()
-    assert old_pip_version not in venv.pip.run("--version", capture=True).stdout
-
-
-def test_remove_requirements(complex_project):
-
-    # Common pipe
-    pipe = complex_project.pipes[complex_project.common]
-
-    # Create the virtualenv
-    venv = Virtualenv(pipe)
-    venv.create()
-    venv.update()
-
-    # Check the requirement file
-    venv.freeze()
-    with open(pipe.requirements_file, "r") as f:
-        right_requirements = f.read()
-
-    # Install manually a requirement
-    venv.pip.run("install pandas")
-
-    # Check that this function removes the manually inserted requirements
-    venv.update()
-    venv.freeze()
-    with open(pipe.requirements_file, "r") as f:
-        now_requirements = f.read()
-    assert right_requirements == now_requirements
-
-    # Remove requirements from the pipe
-    old_requirements = pipe.requirements
-    pipe.requirements = ["pandas==1.3.3"]
-    venv.update()
-    venv.freeze()
-    with open(pipe.requirements_file, "r") as f:
-        now_requirements = f.read()
-    assert "pandas" in now_requirements
-    assert "python-dateutil" in now_requirements
-    assert "six" in now_requirements
-    for r in old_requirements:
-        assert r not in now_requirements
-
-    # Clean everything
-    Cleaner().manage_from_pipe(pipe)
-    os.remove(pipe.requirements_file)
diff --git a/src/travel/package/travel/tools/outputs/latest_updates.py b/src/travel/package/travel/tools/outputs/latest_updates.py
new file mode 100644
index 0000000..4f12fa4
--- /dev/null
+++ b/src/travel/package/travel/tools/outputs/latest_updates.py
@@ -0,0 +1,25 @@
+import yaml
+
+
+class LatestUpdate:
+
+    def __init__(self, config):
+        self.requirements = config.pop("requirements")
+        self.dependencies = config.pop("dependencies")
+
+    @classmethod
+    def create(cls, requirements, dependencies):
+        return cls({
+            "requirements": requirements,
+            "dependencies": dependencies
+        })
+
+    @classmethod
+    def read(cls, path):
+        with open(path, "r") as f:
+            yml = yaml.load(f, yaml.SafeLoader) or {}
+        return cls(yml)
+
+    def write(self, path):
+        with open(path, "w") as f:
+            yaml.dump(vars(self), f)
diff --git a/src/piper/tests/conftest.py b/src/travel/tests/conftest.py
similarity index 66%
rename from src/piper/tests/conftest.py
rename to src/travel/tests/conftest.py
index 6946ca6..558136c 100644
--- a/src/piper/tests/conftest.py
+++ b/src/travel/tests/conftest.py
@@ -1,7 +1,7 @@
 import os
 
 import pytest
-from piper.config.reader import read_all_pipes
+from travel.config.reader import read_all_bags
 
 
 class ComplexProject:
@@ -13,10 +13,10 @@ def __init__(self, data_location):
         self.microservices = "microservices"
         self.first = "first"
         self.second = "second"
-        self.pipertask_example = "pipertaskexample"
+        self.traveltask_example = "traveltaskexample"
 
-        self.piper_project = os.path.join(data_location, self.name)
-        self.pipes = read_all_pipes(self.piper_project)
+        self.travel_project = os.path.join(data_location, self.name)
+        self.bags = read_all_bags(self.travel_project)
 
 
 @pytest.fixture
diff --git a/src/piper/tests/data/blueprint/locals/pipe.yml b/src/travel/tests/data/complexproject/bag.yml
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/pipe.yml
rename to src/travel/tests/data/complexproject/bag.yml
diff --git a/src/piper/tests/data/complexproject/common/pipe.yml b/src/travel/tests/data/complexproject/common/bag.yml
similarity index 100%
rename from src/piper/tests/data/complexproject/common/pipe.yml
rename to src/travel/tests/data/complexproject/common/bag.yml
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/MANIFEST.in b/src/travel/tests/data/complexproject/common/package/MANIFEST.in
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/piperblueexample/package/MANIFEST.in
rename to src/travel/tests/data/complexproject/common/package/MANIFEST.in
diff --git a/src/piper/tests/data/complexproject/common/package/common/__init__.py b/src/travel/tests/data/complexproject/common/package/common/__init__.py
similarity index 100%
rename from src/piper/tests/data/complexproject/common/package/common/__init__.py
rename to src/travel/tests/data/complexproject/common/package/common/__init__.py
diff --git a/src/piper/tests/data/complexproject/common/package/requirements.txt b/src/travel/tests/data/complexproject/common/package/requirements.txt
similarity index 100%
rename from src/piper/tests/data/complexproject/common/package/requirements.txt
rename to src/travel/tests/data/complexproject/common/package/requirements.txt
diff --git a/src/piper/tests/data/complexproject/common/package/setup.py b/src/travel/tests/data/complexproject/common/package/setup.py
similarity index 100%
rename from src/piper/tests/data/complexproject/common/package/setup.py
rename to src/travel/tests/data/complexproject/common/package/setup.py
diff --git a/src/piper/tests/data/complexproject/microservices/pipe.yml b/src/travel/tests/data/complexproject/microservices/bag.yml
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/pipe.yml
rename to src/travel/tests/data/complexproject/microservices/bag.yml
diff --git a/src/piper/tests/data/complexproject/microservices/first/pipe.yml b/src/travel/tests/data/complexproject/microservices/first/bag.yml
similarity index 71%
rename from src/piper/tests/data/complexproject/microservices/first/pipe.yml
rename to src/travel/tests/data/complexproject/microservices/first/bag.yml
index 356c073..68badaa 100644
--- a/src/piper/tests/data/complexproject/microservices/first/pipe.yml
+++ b/src/travel/tests/data/complexproject/microservices/first/bag.yml
@@ -10,22 +10,22 @@ tasks:
 
   clean:
     pre:
-      - pipertask: pipertaskexample
+      - pipertask: traveltaskexample
         name: deep-clean
 
   setup:
     pre:
-      - pipertask: pipertaskexample
+      - pipertask: traveltaskexample
         name: make-argonauts-1
         config:
           something: a
-      - pipertask: pipertaskexample
+      - pipertask: traveltaskexample
         name: make-argonauts-2
         config:
           something: g
 
     post:
-      - pipertask: pipertaskexample
+      - pipertask: traveltaskexample
         name: make-argonauts-3
         config:
           something: a
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/MANIFEST.in b/src/travel/tests/data/complexproject/microservices/first/package/MANIFEST.in
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/resources/blueprint/package/MANIFEST.in
rename to src/travel/tests/data/complexproject/microservices/first/package/MANIFEST.in
diff --git a/src/piper/tests/data/complexproject/microservices/first/package/first/__init__.py b/src/travel/tests/data/complexproject/microservices/first/package/first/__init__.py
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/first/package/first/__init__.py
rename to src/travel/tests/data/complexproject/microservices/first/package/first/__init__.py
diff --git a/src/piper/tests/data/complexproject/microservices/first/package/requirements.txt b/src/travel/tests/data/complexproject/microservices/first/package/requirements.txt
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/first/package/requirements.txt
rename to src/travel/tests/data/complexproject/microservices/first/package/requirements.txt
diff --git a/src/piper/tests/data/complexproject/microservices/first/package/setup.py b/src/travel/tests/data/complexproject/microservices/first/package/setup.py
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/first/package/setup.py
rename to src/travel/tests/data/complexproject/microservices/first/package/setup.py
diff --git a/src/piper/tests/data/complexproject/microservices/second/pipe.yml b/src/travel/tests/data/complexproject/microservices/second/bag.yml
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/second/pipe.yml
rename to src/travel/tests/data/complexproject/microservices/second/bag.yml
diff --git a/src/piper/tests/data/complexproject/common/package/MANIFEST.in b/src/travel/tests/data/complexproject/microservices/second/package/MANIFEST.in
similarity index 100%
rename from src/piper/tests/data/complexproject/common/package/MANIFEST.in
rename to src/travel/tests/data/complexproject/microservices/second/package/MANIFEST.in
diff --git a/src/piper/tests/data/complexproject/microservices/second/package/requirements.txt b/src/travel/tests/data/complexproject/microservices/second/package/requirements.txt
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/second/package/requirements.txt
rename to src/travel/tests/data/complexproject/microservices/second/package/requirements.txt
diff --git a/src/piper/tests/data/complexproject/microservices/second/package/second/__init__.py b/src/travel/tests/data/complexproject/microservices/second/package/second/__init__.py
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/second/package/second/__init__.py
rename to src/travel/tests/data/complexproject/microservices/second/package/second/__init__.py
diff --git a/src/piper/tests/data/complexproject/pipe.yml b/src/travel/tests/data/complexproject/microservices/second/package/second/__main__.py
similarity index 100%
rename from src/piper/tests/data/complexproject/pipe.yml
rename to src/travel/tests/data/complexproject/microservices/second/package/second/__main__.py
diff --git a/src/piper/tests/data/complexproject/microservices/second/package/setup.py b/src/travel/tests/data/complexproject/microservices/second/package/setup.py
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/second/package/setup.py
rename to src/travel/tests/data/complexproject/microservices/second/package/setup.py
diff --git a/src/piper/tests/data/complexproject/tasks/pipe.yml b/src/travel/tests/data/complexproject/tasks/bag.yml
similarity index 100%
rename from src/piper/tests/data/complexproject/tasks/pipe.yml
rename to src/travel/tests/data/complexproject/tasks/bag.yml
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/pipe.yml b/src/travel/tests/data/complexproject/tasks/traveltaskexample/bag.yml
similarity index 100%
rename from src/piper/tests/data/complexproject/tasks/pipertaskexample/pipe.yml
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/bag.yml
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/LICENSE b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/LICENSE
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/piperblueexample/package/LICENSE
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/LICENSE
diff --git a/src/piper/tests/data/complexproject/microservices/first/package/MANIFEST.in b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/MANIFEST.in
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/first/package/MANIFEST.in
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/MANIFEST.in
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/requirements.txt b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/requirements.txt
similarity index 100%
rename from src/piper/tests/data/complexproject/tasks/pipertaskexample/package/requirements.txt
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/requirements.txt
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/setup.py b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/setup.py
similarity index 77%
rename from src/piper/tests/data/complexproject/tasks/pipertaskexample/package/setup.py
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/setup.py
index 93fc69e..faadf06 100644
--- a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/setup.py
+++ b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/setup.py
@@ -4,7 +4,7 @@
 
 # Function to retrieve resources files
 def _get_resources(package_name):
-    # Get all the resources (also on nested levels)
+    # Get all the resources (also on baged levels)
     res_paths = os.path.join(package_name, "resources")
     all_resources = [os.path.join(folder, file) for folder, _, files in os.walk(res_paths) for file in files]
     # Remove the prefix: start just from "resources"
@@ -20,10 +20,10 @@ def _get_resources(package_name):
 
 # Package configuration
 setup(
-    name="pipertaskexample",
+    name="traveltaskexample",
     version="0.0.0",
-    description="An example of Piper Task",
+    description="An example of Travel Task",
     packages=find_packages(),
-    package_data={"pipertaskexample": _get_resources("pipertaskexample")},
+    package_data={"traveltaskexample": _get_resources("traveltaskexample")},
     install_requires=requirements
 )
diff --git a/src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/__init__.py b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/traveltaskexample/__init__.py
similarity index 100%
rename from src/piper/tests/data/blueprint/locals/piperblueexample/package/piperblueexample/__init__.py
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/traveltaskexample/__init__.py
diff --git a/src/piper/tests/data/complexproject/tasks/pipertaskexample/package/pipertaskexample/task.py b/src/travel/tests/data/complexproject/tasks/traveltaskexample/package/traveltaskexample/task.py
similarity index 100%
rename from src/piper/tests/data/complexproject/tasks/pipertaskexample/package/pipertaskexample/task.py
rename to src/travel/tests/data/complexproject/tasks/traveltaskexample/package/traveltaskexample/task.py
diff --git a/src/travel/tests/data/plan/complex/travel.yml b/src/travel/tests/data/plan/complex/travel.yml
new file mode 100644
index 0000000..4992ea0
--- /dev/null
+++ b/src/travel/tests/data/plan/complex/travel.yml
@@ -0,0 +1,19 @@
+common:
+  plan: gh:travel-tools/cookiecutter-travelplan
+  config:
+    name: common
+    description: bla
+
+microservices:
+
+  first:
+    plan: gh:travel-tools/cookiecutter-travelplan
+    config:
+      name: first
+      description: bla
+
+  second:
+    plan: gh:travel-tools/cookiecutter-travelplan
+    config:
+      name: second
+      description: bla
diff --git a/src/piper/tests/data/simpleproject/pipe.yml b/src/travel/tests/data/simpleproject/nest.yml
similarity index 100%
rename from src/piper/tests/data/simpleproject/pipe.yml
rename to src/travel/tests/data/simpleproject/nest.yml
diff --git a/src/piper/tests/data/complexproject/microservices/second/package/second/MANIFEST.in b/src/travel/tests/data/simpleproject/package/MANIFEST.in
similarity index 100%
rename from src/piper/tests/data/complexproject/microservices/second/package/second/MANIFEST.in
rename to src/travel/tests/data/simpleproject/package/MANIFEST.in
diff --git a/src/piper/tests/data/simpleproject/package/setup.py b/src/travel/tests/data/simpleproject/package/setup.py
similarity index 100%
rename from src/piper/tests/data/simpleproject/package/setup.py
rename to src/travel/tests/data/simpleproject/package/setup.py
diff --git a/src/piper/tests/data/simpleproject/package/simple/__init__.py b/src/travel/tests/data/simpleproject/package/simple/__init__.py
similarity index 100%
rename from src/piper/tests/data/simpleproject/package/simple/__init__.py
rename to src/travel/tests/data/simpleproject/package/simple/__init__.py
diff --git a/src/piper/tests/src/pipefiles/__init__.py b/src/travel/tests/src/adder/__init__.py
similarity index 100%
rename from src/piper/tests/src/pipefiles/__init__.py
rename to src/travel/tests/src/adder/__init__.py
diff --git a/src/travel/tests/src/adder/test_adder.py b/src/travel/tests/src/adder/test_adder.py
new file mode 100644
index 0000000..0c113b8
--- /dev/null
+++ b/src/travel/tests/src/adder/test_adder.py
@@ -0,0 +1,41 @@
+import os
+import shutil
+from pathlib import Path
+from typing import Dict, Any
+
+import yaml
+from travel.cli import planner
+from travel.config.reader import BAG_FILE
+
+
+def test_plan(data_location):
+
+    # Folders and files
+    plan_folder = Path(data_location)/"plan"
+    travel_file_location = plan_folder/"complex"
+    travel_file = travel_file_location/"travel.yml"
+    name = "example"
+
+    # Create the bags
+    planner.run(travel_file_location, name)
+    output_folder = travel_file_location/name
+
+    # Check the bags structure against the plan file
+    with open(travel_file) as f:
+        yml = yaml.load(f, Loader=yaml.SafeLoader)
+    _is_created(output_folder, yml)
+
+    # Cleanup
+    shutil.rmtree(output_folder)
+
+
+def _is_created(context: Path, yml: Dict[str, Any]):
+
+    assert context.is_dir()
+    assert (context/BAG_FILE).is_file()
+
+    for bag, properties in yml.items():
+        if "plan" not in properties:
+            _is_created(context/bag, properties)
+
+
diff --git a/src/piper/tests/src/requirements/__init__.py b/src/travel/tests/src/bags/__init__.py
similarity index 100%
rename from src/piper/tests/src/requirements/__init__.py
rename to src/travel/tests/src/bags/__init__.py
diff --git a/src/travel/tests/src/bags/test_bag_file_reader.py b/src/travel/tests/src/bags/test_bag_file_reader.py
new file mode 100644
index 0000000..f25a22a
--- /dev/null
+++ b/src/travel/tests/src/bags/test_bag_file_reader.py
@@ -0,0 +1,34 @@
+def test_finder(complex_project):
+    # Number of bags
+    bags = complex_project.bags
+    assert len(bags) == 7
+    assert len([
+        b for b in bags.keys()
+        if b in [
+            complex_project.name, 
+            complex_project.common, 
+            complex_project.tasks, 
+            complex_project.microservices, 
+            complex_project.first, 
+            complex_project.second, 
+            complex_project.traveltask_example
+        ]
+    ]) == 7
+
+
+def test_structure(complex_project):
+    bags = complex_project.bags
+    # Root context
+    assert len([b for b in bags.values() if b.root_context != complex_project.travel_project]) == 0
+    # Dependencies
+    assert bags[complex_project.second].flat_dependencies(with_current=True) == [
+        bags[complex_project.common],
+        bags[complex_project.first],
+        bags[complex_project.second]
+    ]
+
+
+def test_read_tasks(complex_project):
+    # Tasks reader
+    bags = complex_project.bags
+    assert len(bags[complex_project.first].tasks["setup"]["pre"]) == 2
diff --git a/src/piper/tests/src/sanitizers/__init__.py b/src/travel/tests/src/planner/__init__.py
similarity index 100%
rename from src/piper/tests/src/sanitizers/__init__.py
rename to src/travel/tests/src/planner/__init__.py
diff --git a/src/travel/tests/src/planner/test_planner.py b/src/travel/tests/src/planner/test_planner.py
new file mode 100644
index 0000000..d2a7b01
--- /dev/null
+++ b/src/travel/tests/src/planner/test_planner.py
@@ -0,0 +1,23 @@
+import os
+import shutil
+from pathlib import Path
+
+from travel.cli import adder
+
+
+def test_add(data_location):
+
+    # Add a bag
+    adder.run(
+        data_location,
+        "gh:travel-tools/cookiecutter-travelplan",
+        no_input=True,
+        config=["name=ok"]
+    )
+
+    # Check creation
+    output_folder = os.path.join(data_location, "ok")
+    assert Path(output_folder).is_dir()
+
+    # Cleanup
+    shutil.rmtree(output_folder)
diff --git a/src/travel/tests/src/requirements/__init__.py b/src/travel/tests/src/requirements/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/piper/tests/src/requirements/test_scopes.py b/src/travel/tests/src/requirements/test_scopes.py
similarity index 55%
rename from src/piper/tests/src/requirements/test_scopes.py
rename to src/travel/tests/src/requirements/test_scopes.py
index e8b42c7..8ccadb3 100644
--- a/src/piper/tests/src/requirements/test_scopes.py
+++ b/src/travel/tests/src/requirements/test_scopes.py
@@ -1,22 +1,22 @@
 from pathlib import Path
 
-from piper.custom.scopes.scoped_venvs import ScopedVirtualenvs
+from travel.custom.scopes.scoped_venvs import ScopedVirtualenvs
 
 
 def test_scopes(complex_project):
 
-    # Pipe
-    pipe = complex_project.pipes[complex_project.first]
+    # Bag
+    bag = complex_project.bags[complex_project.first]
 
     # Create all scopes
-    scopes = ScopedVirtualenvs(pipe, touch_requirements_file=True)
+    scopes = ScopedVirtualenvs(bag, touch_requirements_file=True)
     scopes.create_all()
     scopes.update_all()
     scopes.freeze_all()
 
     # Verify envs
     for scope in scopes.envs.keys():
-        assert (Path(pipe.location)/f"venv-{pipe.name}-{scope}").is_dir()
+        assert (Path(bag.location)/f"venv-{bag.name}-{scope}").is_dir()
 
     # Check for single env
     for scope, env in scopes.envs.items():
@@ -26,9 +26,9 @@ def test_scopes(complex_project):
             requirements = f.read()
 
         # Are there the extra requirements?
-        for req in pipe.scopes[scope].requirements:
+        for req in bag.scopes[scope].requirements:
             assert req in requirements
 
-        # Are there the pipe requirements?
-        for req in pipe.requirements:
+        # Are there the bag requirements?
+        for req in bag.requirements:
             assert req in requirements
diff --git a/src/travel/tests/src/requirements/test_venv.py b/src/travel/tests/src/requirements/test_venv.py
new file mode 100644
index 0000000..b73a4f8
--- /dev/null
+++ b/src/travel/tests/src/requirements/test_venv.py
@@ -0,0 +1,112 @@
+import os
+from pathlib import Path
+
+from travel.cli.cleaner import Cleaner
+from travel.config.sanitizers.pip_sanitizer import LATEST_PIP
+from travel.tools.base_venv import LATEST_UPDATE
+from travel.tools.outputs.latest_updates import LatestUpdate
+from travel.tools.venv import Virtualenv
+
+
+def test_quick_setup(complex_project):
+
+    # Bag
+    bag = complex_project.bags[complex_project.common]
+
+    # Create the virtualenv
+    venv = Virtualenv(bag)
+    venv.create()
+    venv.update()
+
+    # Test that no update will be performed on another update
+    assert venv.update() is False
+
+    # Test that update will be performed first, and then no more
+    bag.requirements = []
+    assert venv.update() is True
+    venv.freeze()
+    assert venv.update() is False
+
+    # Test update in case of venv mismatch
+    venv.pip.install("jsonargon==0.1.1")
+    assert venv.update() is True
+    assert venv.update() is False
+
+    # Remove the file
+    latest_update_path = Path(venv.path)/LATEST_UPDATE
+    os.remove(latest_update_path)
+    assert venv.update() is True
+    assert venv.update() is False
+
+    # Test update in case of dependencies mismatch (simulate a change on filesystem)
+    update = LatestUpdate.read(latest_update_path)
+    update.dependencies = ["fake"]
+    update.write(latest_update_path)
+    assert venv.update() is True
+    assert venv.update() is False
+
+
+def test_pip_version(complex_project):
+
+    # Bag
+    bag = complex_project.bags[complex_project.common]
+
+    # Create the virtualenv
+    venv = Virtualenv(bag)
+    venv.create()
+    venv.update()
+
+    # Check pip version
+    assert bag.pip.version in venv.pip.run("--version", capture=True).stdout
+
+    # Force pip version to latest
+    old_pip_version = bag.pip.version
+    bag.pip.version = LATEST_PIP
+
+    # Update pip and check if the old version is no more there
+    venv.update()
+    assert old_pip_version not in venv.pip.run("--version", capture=True).stdout
+
+
+def test_remove_requirements(complex_project):
+
+    # Common bag
+    bag = complex_project.bags[complex_project.common]
+
+    # Create the virtualenv
+    venv = Virtualenv(bag)
+    venv.create()
+    venv.update()
+
+    # Check the requirement file
+    venv.freeze()
+    with open(bag.requirements_file, "r") as f:
+        right_requirements = f.read()
+
+    # Install manually a requirement
+    venv.pip.run("install pandas")
+
+    # Check that this function removes the manually inserted requirements
+    venv.update()
+    venv.freeze()
+    with open(bag.requirements_file, "r") as f:
+        now_requirements = f.read()
+    assert right_requirements == now_requirements
+
+    # Remove requirements from the bag
+    old_requirements = bag.requirements
+    bag.requirements = ["pandas==1.3.3"]
+    venv.update()
+    venv.freeze()
+    with open(bag.requirements_file, "r") as f:
+        now_requirements = f.read()
+    assert "pandas" in now_requirements
+    assert "python-dateutil" in now_requirements
+    assert "six" in now_requirements
+    for r in old_requirements:
+        assert r not in now_requirements
+
+    # Clean everything
+    Cleaner().manage_from_bag(bag)
+    os.remove(bag.requirements_file)
+
diff --git a/src/travel/tests/src/sanitizers/__init__.py b/src/travel/tests/src/sanitizers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/piper/tests/src/sanitizers/test_pip.py b/src/travel/tests/src/sanitizers/test_pip.py
similarity index 85%
rename from src/piper/tests/src/sanitizers/test_pip.py
rename to src/travel/tests/src/sanitizers/test_pip.py
index 9098d1a..259a5a0 100644
--- a/src/piper/tests/src/sanitizers/test_pip.py
+++ b/src/travel/tests/src/sanitizers/test_pip.py
@@ -1,5 +1,5 @@
 import pytest
-from piper.config.sanitizers.pip_sanitizer import sanitize_package, sanitize_version, sanitize_versioned_package
+from travel.config.sanitizers.pip_sanitizer import sanitize_package, sanitize_version, sanitize_versioned_package
 
 
 def test_pip_sanitizer():