I have no idea why other answers say that your approach is not recommended, on the contrary, it is the officially recommended one (as of 2023). Core Python developers use this approach. What you need to make sure is to define the __version__ string statically, not dynamically (ie, do not fetch at runtime from a text file, it must be hardcoded in a Python string in a Python file, to be statically available at build time).
Since setuptools v61, you can indeed set a __version__ attribute in your package's __init__.py file as you did, and then dynamically fetch it into your pyproject.toml like so:
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = ["mypackage"]
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {attr = "mypackage.__version__"}
Note however that if you also want to access mypackage.__version__ from inside mypackage, you should NOT from . import __version__ because if you also import things in your __init__.py, this will cause an infinite import loop! Instead, you need to implement a function to read (not import!) __init__.py and extract the version string, fortunately the official documentation nowadays provides an easy example:
import codecs
import os.path
def read(rel_path):
    here = os.path.abspath(os.path.dirname(__file__))
    with codecs.open(os.path.join(here, rel_path), 'r') as fp:
        return fp.read()
def get_version(rel_path):
    for line in read(rel_path).splitlines():
        if line.startswith('__version__'):
            delim = '"' if '"' in line else "'"
            return line.split(delim)[1]
    else:
        raise RuntimeError("Unable to find version string.")
version = get_version("mypackage/__init__.py")
If you only want to support Py3, then you are all set, you can stop reading here.
But if you need to use an older version of setuptools, eg if you need to support Python 2.7 (which is very disadvised but some legacy projects may need it), then you can instead use setup.cfg, which supports dynamic version since an earlier version of setuptools:
[metadata]
name = mypackage
version = attr: mypackage.__version__
Note that you still need to have an almost empty setup.py for the setup.cfg to work, otherwise setuptools will choke (it's an official rule):
from setuptools import setup
setup()
But then, if you also want your module to have both a setup.cfg and setup.py to support Py2 and also have a pyproject.toml to support Py3, you will notice that building may fail under Py2 because of requiring a setuptools version too high (the latest on Py2 being v41.1.1). Indeed, even if on Py2 building is done through setup.cfg and setup.py, if you updated pip, then it will STILL access pyproject.toml and access the [build-system] table, because this is the only place where a module can specify build-time requirements, according to PEP 517.
To fix this issue, you need to edit the build requirement in pyproject.toml to specify the python version (see PEP 508 about dependencies specifications):
[build-system]
requires = ["setuptools>=44;python_version<'3'", "setuptools>=61;python_version>='3'"]
build-backend = "setuptools.build_meta"
Then your package should build fine (with the build module or pip install --pep-517) under both Py3 and Py2, using only the pyproject.toml for the former, or setup.cfg for the latter (with a bit of pyproject.toml just for the build-system).
     
    
setup.pysince its Python code, but thepyproject.tomlsyntax is a bit confusing.pyproject.tomlandsetup.py? You should use one or the other.