1. Allow build, lint, test & bash with the celery container using `make` commands.
2. Added CI to build the docker image to make sure it doesn't get broken in a future PR without knowing.
3. Default to Python 3.11 in the Dockerfile if not set otherwise via .python-version (pyenv).

For example running `make docker-lint` from the `celery` repo root:
```bash
make docker-lint

[+] Creating 4/0
 ✔ Container docker-azurite-1   Running                                                                                                                                           0.0s
 ✔ Container docker-rabbit-1    Running                                                                                                                                           0.0s
 ✔ Container docker-dynamodb-1  Running                                                                                                                                           0.0s
 ✔ Container docker-redis-1     Running                                                                                                                                           0.0s
ROOT: will run in automatically provisioned tox, host /home/developer/.pyenv/versions/3.11.5/bin/python3.11 is missing [requires (has)]: tox-gh-actions
ROOT: recreate env because python changed version_info=[3, 9, 13, 'final', 0]->[3, 11, 5, 'final', 0] | executable='/Users/nusnus/.pyenv/versions/3.9.13/bin/python3.9'->'/home/developer/.pyenv/versions/3.11.5/bin/python3.11' | virtualenv version='20.24.4'->'20.24.5'
ROOT: remove tox env folder /home/developer/celery/.tox/.tox
ROOT: install_deps> python -I -m pip install tox tox-gh-actions
ROOT: provision> .tox/.tox/bin/python -m tox -e lint
ROOT: tox-gh-actions won't override envlist because tox is not running in GitHub Actions
.pkg: _optional_hooks> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_editable> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_editable> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
lint: install_package> python -I -m pip install --force-reinstall --no-deps /home/developer/celery/.tox/.tmp/package/17/celery-5.3.4-0.editable-py3-none-any.whl
lint: commands[0]> pre-commit run --all-files --show-diff-on-failure
[INFO] Initializing environment for https://github.com/asottile/pyupgrade.
[INFO] Initializing environment for https://github.com/PyCQA/flake8.
[INFO] Initializing environment for https://github.com/asottile/yesqa.
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[INFO] Initializing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Installing environment for https://github.com/asottile/pyupgrade.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/PyCQA/flake8.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/asottile/yesqa.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
pyupgrade................................................................Passed
flake8...................................................................Passed
Strip unnecessary `# noqa`s..............................................Passed
check for merge conflicts................................................Passed
check toml...............................................................Passed
check yaml...............................................................Passed
mixed line ending........................................................Passed
isort....................................................................Passed
mypy.....................................................................Passed
.pkg: _exit> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
  lint: OK (35.25=setup[3.34]+cmd[31.92] seconds)
  congratulations :) (37.49 seconds)
```


The `make help` is also modified:
```bash
make help
docs                 - Build documentation.
test-all             - Run tests for all supported python versions.
distcheck ---------- - Check distribution for problems.
  test               - Run unittests using current python.
  lint ------------  - Check codebase for problems.
    apicheck         - Check API reference coverage.
    configcheck      - Check configuration reference coverage.
    readmecheck      - Check README.rst encoding.
    contribcheck     - Check CONTRIBUTING.rst encoding
    flakes --------  - Check code for syntax and style errors.
      flakecheck     - Run flake8 on the source code.
      flakepluscheck - Run flakeplus on the source code.
readme               - Regenerate README.rst file.
contrib              - Regenerate CONTRIBUTING.rst file
clean-dist --------- - Clean all distribution build artifacts.
  clean-git-force    - Remove all uncommitted files.
  clean ------------ - Non-destructive clean
    clean-pyc        - Remove .pyc/__pycache__ files
    clean-docs       - Remove documentation build artifacts.
    clean-build      - Remove setup artifacts.
bump                 - Bump patch version number.
bump-minor           - Bump minor version number.
bump-major           - Bump major version number.
release              - Make PyPI release.

Docker-specific commands:
  docker-build			- Build celery docker container.
  docker-lint        		- Run tox -e lint on docker container.
  docker-unit-tests		- Run unit tests on docker container, use '-- -k <TEST NAME>' for specific test run.
  docker-bash        		- Get a bash shell inside the container.
```
Celery Docker Upgrades. 1. Allow build, lint, test & bash with the celery container using `make` commands.
2. Added CI to build the docker image to make sure it doesn't get broken in a future PR without knowing.
3. Default to Python 3.11 in the Dockerfile if not set otherwise via .python-version (pyenv).

For example running `make docker-lint` from the `celery` repo root:
```bash
make docker-lint

[+] Creating 4/0
 ✔ Container docker-azurite-1   Running                                                                                                                                           0.0s
 ✔ Container docker-rabbit-1    Running                                                                                                                                           0.0s
 ✔ Container docker-dynamodb-1  Running                                                                                                                                           0.0s
 ✔ Container docker-redis-1     Running                                                                                                                                           0.0s
ROOT: will run in automatically provisioned tox, host /home/developer/.pyenv/versions/3.11.5/bin/python3.11 is missing [requires (has)]: tox-gh-actions
ROOT: recreate env because python changed version_info=[3, 9, 13, 'final', 0]->[3, 11, 5, 'final', 0] | executable='/Users/nusnus/.pyenv/versions/3.9.13/bin/python3.9'->'/home/developer/.pyenv/versions/3.11.5/bin/python3.11' | virtualenv version='20.24.4'->'20.24.5'
ROOT: remove tox env folder /home/developer/celery/.tox/.tox
ROOT: install_deps> python -I -m pip install tox tox-gh-actions
ROOT: provision> .tox/.tox/bin/python -m tox -e lint
ROOT: tox-gh-actions won't override envlist because tox is not running in GitHub Actions
.pkg: _optional_hooks> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_editable> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_editable> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
lint: install_package> python -I -m pip install --force-reinstall --no-deps /home/developer/celery/.tox/.tmp/package/17/celery-5.3.4-0.editable-py3-none-any.whl
lint: commands[0]> pre-commit run --all-files --show-diff-on-failure
[INFO] Initializing environment for https://github.com/asottile/pyupgrade.
[INFO] Initializing environment for https://github.com/PyCQA/flake8.
[INFO] Initializing environment for https://github.com/asottile/yesqa.
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[INFO] Initializing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Installing environment for https://github.com/asottile/pyupgrade.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/PyCQA/flake8.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/asottile/yesqa.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
pyupgrade................................................................Passed
flake8...................................................................Passed
Strip unnecessary `# noqa`s..............................................Passed
check for merge conflicts................................................Passed
check toml...............................................................Passed
check yaml...............................................................Passed
mixed line ending........................................................Passed
isort....................................................................Passed
mypy.....................................................................Passed
.pkg: _exit> python /home/developer/celery/.tox/.tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
  lint: OK (35.25=setup[3.34]+cmd[31.92] seconds)
  congratulations :) (37.49 seconds)
```


The `make help` is also modified:
```bash
make help
docs                 - Build documentation.
test-all             - Run tests for all supported python versions.
distcheck ---------- - Check distribution for problems.
  test               - Run unittests using current python.
  lint ------------  - Check codebase for problems.
    apicheck         - Check API reference coverage.
    configcheck      - Check configuration reference coverage.
    readmecheck      - Check README.rst encoding.
    contribcheck     - Check CONTRIBUTING.rst encoding
    flakes --------  - Check code for syntax and style errors.
      flakecheck     - Run flake8 on the source code.
      flakepluscheck - Run flakeplus on the source code.
readme               - Regenerate README.rst file.
contrib              - Regenerate CONTRIBUTING.rst file
clean-dist --------- - Clean all distribution build artifacts.
  clean-git-force    - Remove all uncommitted files.
  clean ------------ - Non-destructive clean
    clean-pyc        - Remove .pyc/__pycache__ files
    clean-docs       - Remove documentation build artifacts.
    clean-build      - Remove setup artifacts.
bump                 - Bump patch version number.
bump-minor           - Bump minor version number.
bump-major           - Bump major version number.
release              - Make PyPI release.

Docker-specific commands:
  docker-build			- Build celery docker container.
  docker-lint        		- Run tox -e lint on docker container.
  docker-unit-tests		- Run unit tests on docker container, use '-- -k <TEST NAME>' for specific test run.
  docker-bash        		- Get a bash shell inside the container.
```
from time import sleep

from config import app
from visitors import FullVisitor, MonitoringIdStampingVisitor, MyStampingVisitor

from celery import Task
from celery.canvas import Signature, maybe_signature
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)


def log_demo(running_task):
    request, name = running_task.request, running_task.name + running_task.request.argsrepr
    if hasattr(request, "stamps"):
        stamps = request.stamps or {}
        stamped_headers = request.stamped_headers or []

    if stamps and stamped_headers:
        logger.critical(f"Found {name}.stamps: {stamps}")
        logger.critical(f"Found {name}.stamped_headers: {stamped_headers}")
    else:
        logger.critical(f"Running {name} without stamps")

    links = request.callbacks or []
    for link in links:
        link = maybe_signature(link)
        logger.critical(f"Found {name}.link: {link}")
        stamped_headers = link.options.get("stamped_headers", [])
        stamps = {stamp: link.options[stamp] for stamp in stamped_headers}

        if stamps and stamped_headers:
            logger.critical(f"Found {name}.link stamps: {stamps}")
            logger.critical(f"Found {name}.link stamped_headers: {stamped_headers}")
        else:
            logger.critical(f"Running {name}.link without stamps")


class StampOnReplace(Task):
    """Custom task for stamping on replace"""

    def on_replace(self, sig: Signature):
        logger.warning(f"StampOnReplace: {sig}.stamp(FullVisitor())")
        sig.stamp(FullVisitor())
        logger.warning(f"StampOnReplace: {sig}.stamp(MyStampingVisitor())")
        sig.stamp(MyStampingVisitor())
        return super().on_replace(sig)


class MonitoredTask(Task):
    def on_replace(self, sig: Signature):
        logger.warning(f"MonitoredTask: {sig}.stamp(MonitoringIdStampingVisitor())")
        sig.stamp(MonitoringIdStampingVisitor(), append_stamps=False)
        return super().on_replace(sig)


@app.task(bind=True)
def identity_task(self, x):
    """Identity function"""
    log_demo(self)
    return x


@app.task(bind=True, base=MonitoredTask)
def replaced_identity(self: MonitoredTask, x):
    log_demo(self)
    logger.warning("Stamping identity_task with MonitoringIdStampingVisitor() before replace")
    replaced_task = identity_task.s(x)
    # These stamps should be overridden by the stamps from MonitoredTask.on_replace()
    replaced_task.stamp(MonitoringIdStampingVisitor())
    return self.replace(replaced_task)


@app.task(bind=True, base=StampOnReplace)
def identity(self: Task, x):
    log_demo(self)
    return self.replace(replaced_identity.s(x))


@app.task
def mul(x: int, y: int) -> int:
    """Multiply two numbers"""
    return x * y


@app.task
def xsum(numbers: list) -> int:
    """Sum a list of numbers"""
    return sum(numbers)


@app.task
def waitfor(seconds: int) -> None:
    """Wait for "seconds" seconds, ticking every second."""
    print(f"Waiting for {seconds} seconds...")
    for i in range(seconds):
        sleep(1)
        print(f"{i+1} seconds passed")


@app.task(bind=True, base=StampOnReplace)
def wait_for_revoke(self: StampOnReplace, seconds: int) -> None:
    """Replace this task with a new task that waits for "seconds" seconds."""
    self.replace(waitfor.s(seconds))

from time import sleep

from config import app
from visitors import FullVisitor, MonitoringIdStampingVisitor, MyStampingVisitor

from celery import Task
from celery.canvas import Signature, maybe_signature
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)


def log_demo(running_task):
    request, name = running_task.request, running_task.name + running_task.request.argsrepr
    if hasattr(request, "stamps"):
        stamps = request.stamps or {}
        stamped_headers = request.stamped_headers or []

    if stamps and stamped_headers:
        logger.critical(f"Found {name}.stamps: {stamps}")
        logger.critical(f"Found {name}.stamped_headers: {stamped_headers}")
    else:
        logger.critical(f"Running {name} without stamps")

    links = request.callbacks or []
    for link in links:
        link = maybe_signature(link)
        logger.critical(f"Found {name}.link: {link}")
        stamped_headers = link.options.get("stamped_headers", [])
        stamps = {stamp: link.options[stamp] for stamp in stamped_headers}

        if stamps and stamped_headers:
            logger.critical(f"Found {name}.link stamps: {stamps}")
            logger.critical(f"Found {name}.link stamped_headers: {stamped_headers}")
        else:
            logger.critical(f"Running {name}.link without stamps")


class StampOnReplace(Task):
    """Custom task for stamping on replace"""

    def on_replace(self, sig: Signature):
        logger.warning(f"StampOnReplace: {sig}.stamp(FullVisitor())")
        sig.stamp(FullVisitor())
        logger.warning(f"StampOnReplace: {sig}.stamp(MyStampingVisitor())")
        sig.stamp(MyStampingVisitor())
        return super().on_replace(sig)


class MonitoredTask(Task):
    def on_replace(self, sig: Signature):
        logger.warning(f"MonitoredTask: {sig}.stamp(MonitoringIdStampingVisitor())")
        sig.stamp(MonitoringIdStampingVisitor(), append_stamps=False)
        return super().on_replace(sig)


@app.task(bind=True)
def identity_task(self, x):
    """Identity function"""
    log_demo(self)
    return x


@app.task(bind=True, base=MonitoredTask)
def replaced_identity(self: MonitoredTask, x):
    log_demo(self)
    logger.warning("Stamping identity_task with MonitoringIdStampingVisitor() before replace")
    replaced_task = identity_task.s(x)
    # These stamps should be overridden by the stamps from MonitoredTask.on_replace()
    replaced_task.stamp(MonitoringIdStampingVisitor())
    return self.replace(replaced_task)


@app.task(bind=True, base=StampOnReplace)
def identity(self: Task, x):
    log_demo(self)
    return self.replace(replaced_identity.s(x))


@app.task
def mul(x: int, y: int) -> int:
    """Multiply two numbers"""
    return x * y


@app.task
def xsum(numbers: list) -> int:
    """Sum a list of numbers"""
    return sum(numbers)


@app.task
def waitfor(seconds: int) -> None:
    """Wait for "seconds" seconds, ticking every second."""
    print(f"Waiting for {seconds} seconds...")
    for i in range(seconds):
        sleep(1)
        print(f"{i+1} seconds passed")


@app.task(bind=True, base=StampOnReplace)
def wait_for_revoke(self: StampOnReplace, seconds: int) -> None:
    """Replace this task with a new task that waits for "seconds" seconds."""
    self.replace(waitfor.s(seconds))
