diff --git a/packages/griffe/LICENSE b/packages/griffe/LICENSE
new file mode 100644
index 00000000..8becbc45
--- /dev/null
+++ b/packages/griffe/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2021, Timothée Mazzucotelli
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/packages/griffe/README.md b/packages/griffe/README.md
new file mode 100644
index 00000000..7fbc0b5b
--- /dev/null
+++ b/packages/griffe/README.md
@@ -0,0 +1,115 @@
+# Griffe
+
+[](https://github.com/mkdocstrings/griffe/actions?query=workflow%3Aci)
+[](https://mkdocstrings.github.io/griffe/)
+[](https://pypi.org/project/griffe/)
+[](https://app.gitter.im/#/room/#mkdocstrings_griffe:gitter.im)
+[](https://app.radicle.at/nodes/seed.radicle.at/rad:z4M5XTPDD4Wh1sm8iPCenF85J3z8Z)
+
+
+
+Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API.
+
+Griffe, pronounced "grif" (`/ɡʁif/`), is a french word that means "claw",
+but also "signature" in a familiar way. "On reconnaît bien là sa griffe."
+
+- [User guide](https://mkdocstrings.github.io/griffe/guide/users/)
+- [Contributor guide](https://mkdocstrings.github.io/griffe/guide/contributors/)
+- [API reference](https://mkdocstrings.github.io/griffe/reference/api/)
+
+## Installation
+
+```bash
+pip install griffe
+```
+
+With [`uv`](https://docs.astral.sh/uv/):
+
+```bash
+uv tool install griffe
+```
+
+## Usage
+
+### Dump JSON-serialized API
+
+**On the command line**, pass the names of packages to the `griffe dump` command:
+
+```console
+$ griffe dump httpx fastapi
+{
+ "httpx": {
+ "name": "httpx",
+ ...
+ },
+ "fastapi": {
+ "name": "fastapi",
+ ...
+ }
+}
+```
+
+See the [Serializing chapter](https://mkdocstrings.github.io/griffe/guide/users/serializing/) for more examples.
+
+### Check for API breaking changes
+
+Pass a relative path to the `griffe check` command:
+
+```console
+$ griffe check mypackage --verbose
+mypackage/mymodule.py:10: MyClass.mymethod(myparam):
+Parameter kind was changed:
+ Old: positional or keyword
+ New: keyword-only
+```
+
+For `src` layouts:
+
+```console
+$ griffe check --search src mypackage --verbose
+src/mypackage/mymodule.py:10: MyClass.mymethod(myparam):
+Parameter kind was changed:
+ Old: positional or keyword
+ New: keyword-only
+```
+
+It's also possible to directly **check packages from PyPI.org**
+(or other indexes configured through `PIP_INDEX_URL`).
+This feature is [available to sponsors only](https://mkdocstrings.github.io/griffe/insiders/)
+and requires that you install Griffe with the `pypi` extra:
+
+```bash
+pip install griffe[pypi]
+```
+
+The command syntax is:
+
+```bash
+griffe check package_name -b project-name==2.0 -a project-name==1.0
+```
+
+See the [Checking chapter](https://mkdocstrings.github.io/griffe/guide/users/checking/) for more examples.
+
+### Load and navigate data with Python
+
+**With Python**, loading a package:
+
+```python
+import griffe
+
+fastapi = griffe.load("fastapi")
+```
+
+Finding breaking changes:
+
+```python
+import griffe
+
+previous = griffe.load_git("mypackage", ref="0.2.0")
+current = griffe.load("mypackage")
+
+for breakage in griffe.find_breaking_changes(previous, current):
+ ...
+```
+
+See the [Loading chapter](https://mkdocstrings.github.io/griffe/guide/users/loading/) for more examples.
diff --git a/src/griffe/_internal/py.typed b/packages/griffe/pyproject.toml
similarity index 100%
rename from src/griffe/_internal/py.typed
rename to packages/griffe/pyproject.toml
diff --git a/packages/griffe/src/griffe/__init__.py b/packages/griffe/src/griffe/__init__.py
new file mode 100644
index 00000000..78129ca5
--- /dev/null
+++ b/packages/griffe/src/griffe/__init__.py
@@ -0,0 +1,166 @@
+# This top-level module imports all public names from the package,
+# and exposes them as public objects. We have tests to make sure
+# no object is forgotten in this list.
+
+"""Griffe package.
+
+Signatures for entire Python programs.
+Extract the structure, the frame, the skeleton of your project,
+to generate API documentation or find breaking changes in your API.
+
+The entirety of the public API is exposed here, in the top-level `griffe` module.
+
+All messages written to standard output or error are logged using the `logging` module.
+Our logger's name is set to `"griffe"` and is public (you can rely on it).
+You can obtain the logger from the standard `logging` module: `logging.getLogger("griffe")`.
+Actual logging messages are not part of the public API (they might change without notice).
+
+Raised exceptions throughout the package are part of the public API (you can rely on them).
+Their actual messages are not part of the public API (they might change without notice).
+
+The following paragraphs will help you discover the package's content.
+
+## CLI entrypoints
+
+Griffe provides a command-line interface (CLI) to interact with the package. The CLI entrypoints can be called from Python code.
+
+- [`griffe.main`][]: Run the main program.
+- [`griffe.check`][]: Check for API breaking changes in two versions of the same package.
+- [`griffe.dump`][]: Load packages data and dump it as JSON.
+
+## Loaders
+
+To load API data, Griffe provides several high-level functions.
+
+- [`griffe.load`][]: Load and return a Griffe object.
+- [`griffe.load_git`][]: Load and return a module from a specific Git reference.
+- [`griffe.load_pypi`][]: Load and return a module from a specific package version downloaded using pip.
+
+## Models
+
+The data loaded by Griffe is represented by several classes.
+
+- [`griffe.Module`][]: The class representing a Python module.
+- [`griffe.Class`][]: The class representing a Python class.
+- [`griffe.Function`][]: The class representing a Python function or method.
+- [`griffe.Attribute`][]: The class representing a Python attribute.
+- [`griffe.Alias`][]: This class represents an alias, or indirection, to an object declared in another module.
+
+Additional classes are available to represent other concepts.
+
+- [`griffe.Decorator`][]: This class represents a decorator.
+- [`griffe.Parameters`][]: This class is a container for parameters.
+- [`griffe.Parameter`][]: This class represent a function parameter.
+
+## Agents
+
+Griffe is able to analyze code both statically and dynamically, using the following "agents".
+However most of the time you will only need to use the loaders above.
+
+- [`griffe.visit`][]: Parse and visit a module file.
+- [`griffe.inspect`][]: Inspect a module.
+
+## Serializers
+
+Griffe can serizalize data to dictionary and JSON.
+
+- [`griffe.Object.as_json`][griffe.Object.as_json]
+- [`griffe.Object.from_json`][griffe.Object.from_json]
+- [`griffe.JSONEncoder`][]: JSON encoder for Griffe objects.
+- [`griffe.json_decoder`][]: JSON decoder for Griffe objects.
+
+## API checks
+
+Griffe can compare two versions of the same package to find breaking changes.
+
+- [`griffe.find_breaking_changes`][]: Find breaking changes between two versions of the same API.
+- [`griffe.Breakage`][]: Breakage classes can explain what broke from a version to another.
+
+## Extensions
+
+Griffe supports extensions. You can create your own extension by subclassing the `griffe.Extension` class.
+
+- [`griffe.load_extensions`][]: Load configured extensions.
+- [`griffe.Extension`][]: Base class for Griffe extensions.
+
+## Docstrings
+
+Griffe can parse docstrings into structured data.
+
+Main class:
+
+- [`griffe.Docstring`][]: This class represents docstrings.
+
+Docstring section and element classes all start with `Docstring`.
+
+Docstring parsers:
+
+- [`griffe.parse`][]: Parse the docstring.
+- [`griffe.parse_auto`][]: Parse a docstring by automatically detecting the style it uses.
+- [`griffe.parse_google`][]: Parse a Google-style docstring.
+- [`griffe.parse_numpy`][]: Parse a Numpydoc-style docstring.
+- [`griffe.parse_sphinx`][]: Parse a Sphinx-style docstring.
+
+## Exceptions
+
+Griffe uses several exceptions to signal errors.
+
+- [`griffe.GriffeError`][]: The base exception for all Griffe errors.
+- [`griffe.LoadingError`][]: Exception for loading errors.
+- [`griffe.NameResolutionError`][]: Exception for names that cannot be resolved in a object scope.
+- [`griffe.UnhandledEditableModuleError`][]: Exception for unhandled editables modules, when searching modules.
+- [`griffe.UnimportableModuleError`][]: Exception for modules that cannot be imported.
+- [`griffe.AliasResolutionError`][]: Exception for aliases that cannot be resolved.
+- [`griffe.CyclicAliasError`][]: Exception raised when a cycle is detected in aliases.
+- [`griffe.LastNodeError`][]: Exception raised when trying to access a next or previous node.
+- [`griffe.RootNodeError`][]: Exception raised when trying to use siblings properties on a root node.
+- [`griffe.BuiltinModuleError`][]: Exception raised when trying to access the filepath of a builtin module.
+- [`griffe.ExtensionError`][]: Base class for errors raised by extensions.
+- [`griffe.ExtensionNotLoadedError`][]: Exception raised when an extension could not be loaded.
+- [`griffe.GitError`][]: Exception raised for errors related to Git.
+
+# Expressions
+
+Griffe stores snippets of code (attribute values, decorators, base class, type annotations) as expressions.
+Expressions are basically abstract syntax trees (AST) with a few differences compared to the nodes returned by [`ast`][].
+Griffe provides a few helpers to extract expressions from regular AST nodes.
+
+- [`griffe.get_annotation`][]: Get a type annotation as expression.
+- [`griffe.get_base_class`][]: Get a base class as expression.
+- [`griffe.get_condition`][]: Get a condition as expression.
+- [`griffe.get_expression`][]: Get an expression from an AST node.
+- [`griffe.safe_get_annotation`][]: Get a type annotation as expression, safely (returns `None` on error).
+- [`griffe.safe_get_base_class`][]: Get a base class as expression, safely (returns `None` on error).
+- [`griffe.safe_get_condition`][]: Get a condition as expression, safely (returns `None` on error).
+- [`griffe.safe_get_expression`][]: Get an expression from an AST node, safely (returns `None` on error).
+
+The base class for expressions.
+
+- [`griffe.Expr`][]
+
+Expression classes all start with `Expr`.
+
+# Loggers
+
+If you want to log messages from extensions, get a logger with `get_logger`.
+The `logger` attribute is used by Griffe itself. You can use it to temporarily disable Griffe logging.
+
+- [`griffe.logger`][]: Our global logger, used throughout the library.
+- [`griffe.get_logger`][]: Create and return a new logger instance.
+
+# Helpers
+
+To test your Griffe extensions, or to load API data from code in memory, Griffe provides the following helpers.
+
+- [`griffe.temporary_pyfile`][]: Create a Python file containing the given code in a temporary directory.
+- [`griffe.temporary_pypackage`][]: Create a package containing the given modules in a temporary directory.
+- [`griffe.temporary_visited_module`][]: Create and visit a temporary module with the given code.
+- [`griffe.temporary_visited_package`][]: Create and visit a temporary package.
+- [`griffe.temporary_inspected_module`][]: Create and inspect a temporary module with the given code.
+- [`griffe.temporary_inspected_package`][]: Create and inspect a temporary package.
+"""
+
+from __future__ import annotations
+
+from griffelib import *
+from griffelib import __all__
diff --git a/src/griffe/__main__.py b/packages/griffe/src/griffe/__main__.py
similarity index 100%
rename from src/griffe/__main__.py
rename to packages/griffe/src/griffe/__main__.py
diff --git a/packages/griffe/src/griffe/_internal/cli.py b/packages/griffe/src/griffe/_internal/cli.py
new file mode 100644
index 00000000..f28e4e68
--- /dev/null
+++ b/packages/griffe/src/griffe/_internal/cli.py
@@ -0,0 +1,571 @@
+# This module contains all CLI-related things.
+# Why does this file exist, and why not put this in `__main__`?
+#
+# We might be tempted to import things from `__main__` later,
+# but that will cause problems; the code will get executed twice:
+#
+# - When we run `python -m griffe`, Python will execute
+# `__main__.py` as a script. That means there won't be any
+# `griffe.__main__` in `sys.modules`.
+# - When you import `__main__` it will get executed again (as a module) because
+# there's no `griffe.__main__` in `sys.modules`.
+
+from __future__ import annotations
+
+import argparse
+import json
+import logging
+import os
+import sys
+from datetime import datetime, timezone
+from pathlib import Path
+from typing import IO, TYPE_CHECKING, Any, Callable
+
+import colorama
+from griffelib._internal import debug
+from griffelib._internal.diff import find_breaking_changes
+from griffelib._internal.encoders import JSONEncoder
+from griffelib._internal.enumerations import ExplanationStyle, Parser
+from griffelib._internal.exceptions import ExtensionError, GitError
+from griffelib._internal.extensions.base import load_extensions
+from griffelib._internal.git import _get_latest_tag, _get_repo_root
+from griffelib._internal.loader import GriffeLoader, load, load_git
+from griffelib._internal.logger import logger
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from griffelib._internal.docstrings.parsers import DocstringOptions, DocstringStyle
+ from griffelib._internal.extensions.base import Extension, Extensions
+
+
+DEFAULT_LOG_LEVEL = os.getenv("GRIFFE_LOG_LEVEL", "INFO").upper()
+"""The default log level for the CLI.
+
+This can be overridden by the `GRIFFE_LOG_LEVEL` environment variable.
+"""
+
+
+class _DebugInfo(argparse.Action):
+ def __init__(self, nargs: int | str | None = 0, **kwargs: Any) -> None:
+ super().__init__(nargs=nargs, **kwargs)
+
+ def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
+ debug._print_debug_info()
+ sys.exit(0)
+
+
+def _print_data(data: str, output_file: str | IO | None) -> None:
+ if isinstance(output_file, str):
+ with open(output_file, "w") as fd: # noqa: PTH123
+ print(data, file=fd)
+ else:
+ if output_file is None:
+ output_file = sys.stdout
+ print(data, file=output_file)
+
+
+def _load_packages(
+ packages: Sequence[str],
+ *,
+ extensions: Extensions | None = None,
+ search_paths: Sequence[str | Path] | None = None,
+ docstring_parser: DocstringStyle | Parser | None = None,
+ docstring_options: DocstringOptions | None = None,
+ resolve_aliases: bool = True,
+ resolve_implicit: bool = False,
+ resolve_external: bool | None = None,
+ allow_inspection: bool = True,
+ force_inspection: bool = False,
+ store_source: bool = True,
+ find_stubs_package: bool = False,
+) -> GriffeLoader:
+ # Create a single loader.
+ loader = GriffeLoader(
+ extensions=extensions,
+ search_paths=search_paths,
+ docstring_parser=docstring_parser,
+ docstring_options=docstring_options,
+ allow_inspection=allow_inspection,
+ force_inspection=force_inspection,
+ store_source=store_source,
+ )
+
+ # Load each package.
+ for package in packages:
+ if not package:
+ logger.debug("Empty package name, continuing")
+ continue
+ logger.info("Loading package %s", package)
+ try:
+ loader.load(package, try_relative_path=True, find_stubs_package=find_stubs_package)
+ except ModuleNotFoundError as error:
+ logger.error("Could not find package %s: %s", package, error)
+ except ImportError:
+ logger.exception("Tried but could not import package %s", package)
+ logger.info("Finished loading packages")
+
+ # Resolve aliases.
+ if resolve_aliases:
+ logger.info("Starting alias resolution")
+ unresolved, iterations = loader.resolve_aliases(implicit=resolve_implicit, external=resolve_external)
+ if unresolved:
+ logger.info("%s aliases were still unresolved after %s iterations", len(unresolved), iterations)
+ else:
+ logger.info("All aliases were resolved after %s iterations", iterations)
+ return loader
+
+
+_level_choices = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
+
+
+def _extensions_type(value: str) -> Sequence[str | dict[str, Any]]:
+ try:
+ return json.loads(value)
+ except json.JSONDecodeError:
+ return value.split(",")
+
+
+def get_parser() -> argparse.ArgumentParser:
+ """Return the CLI argument parser.
+
+ Returns:
+ An argparse parser.
+ """
+ usage = "%(prog)s [GLOBAL_OPTS...] COMMAND [COMMAND_OPTS...]"
+ description = "Signatures for entire Python programs. "
+ "Extract the structure, the frame, the skeleton of your project, "
+ "to generate API documentation or find breaking changes in your API."
+ parser = argparse.ArgumentParser(add_help=False, usage=usage, description=description, prog="griffe")
+
+ main_help = "Show this help message and exit. Commands also accept the -h/--help option."
+ subcommand_help = "Show this help message and exit."
+
+ global_options = parser.add_argument_group(title="Global options")
+ global_options.add_argument("-h", "--help", action="help", help=main_help)
+ global_options.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug._get_version()}")
+ global_options.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
+
+ def add_common_options(subparser: argparse.ArgumentParser) -> None:
+ common_options = subparser.add_argument_group(title="Common options")
+ common_options.add_argument("-h", "--help", action="help", help=subcommand_help)
+ search_options = subparser.add_argument_group(title="Search options")
+ search_options.add_argument(
+ "-s",
+ "--search",
+ dest="search_paths",
+ action="append",
+ type=Path,
+ help="Paths to search packages into.",
+ )
+ search_options.add_argument(
+ "-y",
+ "--sys-path",
+ dest="append_sys_path",
+ action="store_true",
+ help="Whether to append `sys.path` to search paths specified with `-s`.",
+ )
+ loading_options = subparser.add_argument_group(title="Loading options")
+ loading_options.add_argument(
+ "-B",
+ "--find-stubs-packages",
+ dest="find_stubs_package",
+ action="store_true",
+ default=False,
+ help="Whether to look for stubs-only packages and merge them with concrete ones.",
+ )
+ loading_options.add_argument(
+ "-e",
+ "--extensions",
+ default={},
+ type=_extensions_type,
+ help="A list of extensions to use.",
+ )
+ loading_options.add_argument(
+ "-X",
+ "--no-inspection",
+ dest="allow_inspection",
+ action="store_false",
+ default=True,
+ help="Disallow inspection of builtin/compiled/not found modules.",
+ )
+ loading_options.add_argument(
+ "-x",
+ "--force-inspection",
+ dest="force_inspection",
+ action="store_true",
+ default=False,
+ help="Force inspection of everything, even when sources are found.",
+ )
+ debug_options = subparser.add_argument_group(title="Debugging options")
+ debug_options.add_argument(
+ "-L",
+ "--log-level",
+ metavar="LEVEL",
+ default=DEFAULT_LOG_LEVEL,
+ choices=_level_choices,
+ type=str.upper,
+ help="Set the log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`.",
+ )
+
+ # ========= SUBPARSERS ========= #
+ subparsers = parser.add_subparsers(
+ dest="subcommand",
+ title="Commands",
+ metavar="COMMAND",
+ prog="griffe",
+ required=True,
+ )
+
+ def add_subparser(command: str, text: str, **kwargs: Any) -> argparse.ArgumentParser:
+ return subparsers.add_parser(command, add_help=False, help=text, description=text, **kwargs)
+
+ # ========= DUMP PARSER ========= #
+ dump_parser = add_subparser("dump", "Load package-signatures and dump them as JSON.")
+ dump_options = dump_parser.add_argument_group(title="Dump options")
+ dump_options.add_argument("packages", metavar="PACKAGE", nargs="+", help="Packages to find, load and dump.")
+ dump_options.add_argument(
+ "-f",
+ "--full",
+ action="store_true",
+ default=False,
+ help="Whether to dump full data in JSON.",
+ )
+ dump_options.add_argument(
+ "-o",
+ "--output",
+ default=sys.stdout,
+ help="Output file. Supports templating to output each package in its own file, with `{package}`.",
+ )
+ dump_options.add_argument(
+ "-d",
+ "--docstyle",
+ dest="docstring_parser",
+ default=None,
+ type=Parser,
+ help="The docstring style to parse.",
+ )
+ dump_options.add_argument(
+ "-D",
+ "--docopts",
+ dest="docstring_options",
+ default={},
+ type=json.loads,
+ help="The options for the docstring parser.",
+ )
+ dump_options.add_argument(
+ "-r",
+ "--resolve-aliases",
+ action="store_true",
+ help="Whether to resolve aliases.",
+ )
+ dump_options.add_argument(
+ "-I",
+ "--resolve-implicit",
+ action="store_true",
+ help="Whether to resolve implicitly exported aliases as well. "
+ "Aliases are explicitly exported when defined in `__all__`.",
+ )
+ dump_options.add_argument(
+ "-U",
+ "--resolve-external",
+ dest="resolve_external",
+ action="store_true",
+ help="Always resolve aliases pointing to external/unknown modules (not loaded directly)."
+ "Default is to resolve only from one module to its private sibling (`ast` -> `_ast`).",
+ )
+ dump_options.add_argument(
+ "--no-resolve-external",
+ dest="resolve_external",
+ action="store_false",
+ help="Never resolve aliases pointing to external/unknown modules (not loaded directly)."
+ "Default is to resolve only from one module to its private sibling (`ast` -> `_ast`).",
+ )
+ dump_options.add_argument(
+ "-S",
+ "--stats",
+ action="store_true",
+ help="Show statistics at the end.",
+ )
+ add_common_options(dump_parser)
+
+ # ========= CHECK PARSER ========= #
+ check_parser = add_subparser("check", "Check for API breakages or possible improvements.")
+ check_options = check_parser.add_argument_group(title="Check options")
+ check_options.add_argument("package", metavar="PACKAGE", help="Package to find, load and check, as path.")
+ check_options.add_argument(
+ "-a",
+ "--against",
+ metavar="REF",
+ help="Older Git reference (commit, branch, tag) to check against. Default: load latest tag.",
+ )
+ check_options.add_argument(
+ "-b",
+ "--base-ref",
+ metavar="BASE_REF",
+ help="Git reference (commit, branch, tag) to check. Default: load current code.",
+ )
+ check_options.add_argument(
+ "--color",
+ dest="color",
+ action="store_true",
+ default=None,
+ help="Force enable colors in the output.",
+ )
+ check_options.add_argument(
+ "--no-color",
+ dest="color",
+ action="store_false",
+ default=None,
+ help="Force disable colors in the output.",
+ )
+ check_options.add_argument("-v", "--verbose", action="store_true", help="Verbose output.")
+ formats = [fmt.value for fmt in ExplanationStyle]
+ check_options.add_argument("-f", "--format", dest="style", choices=formats, default=None, help="Output format.")
+ add_common_options(check_parser)
+
+ return parser
+
+
+def dump(
+ packages: Sequence[str],
+ *,
+ output: str | IO | None = None,
+ full: bool = False,
+ docstring_parser: DocstringStyle | Parser | None = None,
+ docstring_options: DocstringOptions | None = None,
+ extensions: Sequence[str | dict[str, Any] | Extension | type[Extension]] | None = None,
+ resolve_aliases: bool = False,
+ resolve_implicit: bool = False,
+ resolve_external: bool | None = None,
+ search_paths: Sequence[str | Path] | None = None,
+ find_stubs_package: bool = False,
+ append_sys_path: bool = False,
+ allow_inspection: bool = True,
+ force_inspection: bool = False,
+ stats: bool = False,
+) -> int:
+ """Load packages data and dump it as JSON.
+
+ Parameters:
+ packages: The packages to load and dump.
+ output: Where to output the JSON-serialized data.
+ full: Whether to output full or minimal data.
+ docstring_parser: The docstring parser to use. By default, no parsing is done.
+ docstring_options: Docstring parsing options.
+ resolve_aliases: Whether to resolve aliases (indirect objects references).
+ resolve_implicit: Whether to resolve every alias or only the explicitly exported ones.
+ resolve_external: Whether to load additional, unspecified modules to resolve aliases.
+ Default is to resolve only from one module to its private sibling (`ast` -> `_ast`).
+ extensions: The extensions to use.
+ search_paths: The paths to search into.
+ find_stubs_package: Whether to search for stubs-only packages.
+ If both the package and its stubs are found, they'll be merged together.
+ If only the stubs are found, they'll be used as the package itself.
+ append_sys_path: Whether to append the contents of `sys.path` to the search paths.
+ allow_inspection: Whether to allow inspecting modules when visiting them is not possible.
+ force_inspection: Whether to force using dynamic analysis when loading data.
+ stats: Whether to compute and log stats about loading.
+
+ Returns:
+ `0` for success, `1` for failure.
+ """
+ # Prepare options.
+ per_package_output = False
+ if isinstance(output, str) and output.format(package="package") != output:
+ per_package_output = True
+
+ search_paths = list(search_paths) if search_paths else []
+ if append_sys_path:
+ search_paths.extend(sys.path)
+
+ try:
+ loaded_extensions = load_extensions(*(extensions or ()))
+ except ExtensionError:
+ logger.exception("Could not load extensions")
+ return 1
+
+ # Load packages.
+ loader = _load_packages(
+ packages,
+ extensions=loaded_extensions,
+ search_paths=search_paths,
+ docstring_parser=docstring_parser,
+ docstring_options=docstring_options,
+ resolve_aliases=resolve_aliases,
+ resolve_implicit=resolve_implicit,
+ resolve_external=resolve_external,
+ allow_inspection=allow_inspection,
+ force_inspection=force_inspection,
+ store_source=False,
+ find_stubs_package=find_stubs_package,
+ )
+ data_packages = loader.modules_collection.members
+
+ # Serialize and dump packages.
+ started = datetime.now(tz=timezone.utc)
+ if per_package_output:
+ for package_name, data in data_packages.items():
+ serialized = data.as_json(indent=2, full=full, sort_keys=True)
+ _print_data(serialized, output.format(package=package_name)) # type: ignore[union-attr]
+ else:
+ serialized = json.dumps(data_packages, cls=JSONEncoder, indent=2, full=full, sort_keys=True)
+ _print_data(serialized, output)
+ elapsed = datetime.now(tz=timezone.utc) - started
+
+ if stats:
+ loader_stats = loader.stats()
+ loader_stats.time_spent_serializing = elapsed.microseconds
+ logger.info(loader_stats.as_text())
+
+ return 0 if len(data_packages) == len(packages) else 1
+
+
+def check(
+ package: str | Path,
+ against: str | None = None,
+ against_path: str | Path | None = None,
+ *,
+ base_ref: str | None = None,
+ extensions: Sequence[str | dict[str, Any] | Extension | type[Extension]] | None = None,
+ search_paths: Sequence[str | Path] | None = None,
+ append_sys_path: bool = False,
+ find_stubs_package: bool = False,
+ allow_inspection: bool = True,
+ force_inspection: bool = False,
+ verbose: bool = False,
+ color: bool | None = None,
+ style: str | ExplanationStyle | None = None,
+) -> int:
+ """Check for API breaking changes in two versions of the same package.
+
+ Parameters:
+ package: The package to load and check.
+ against: Older Git reference (commit, branch, tag) to check against.
+ against_path: Path when the "against" reference is checked out.
+ base_ref: Git reference (commit, branch, tag) to check.
+ extensions: The extensions to use.
+ search_paths: The paths to search into.
+ append_sys_path: Whether to append the contents of `sys.path` to the search paths.
+ allow_inspection: Whether to allow inspecting modules when visiting them is not possible.
+ force_inspection: Whether to force using dynamic analysis when loading data.
+ verbose: Use a verbose output.
+
+ Returns:
+ `0` for success, `1` for failure.
+ """
+ # Prepare options.
+ search_paths = list(search_paths) if search_paths else []
+ if append_sys_path:
+ search_paths.extend(sys.path)
+
+ against_path = against_path or package
+ try:
+ against = against or _get_latest_tag(package)
+ repository = _get_repo_root(against_path)
+ except GitError as error:
+ print(f"griffe: error: {error}", file=sys.stderr)
+ return 2
+
+ try:
+ loaded_extensions = load_extensions(*(extensions or ()))
+ except ExtensionError:
+ logger.exception("Could not load extensions")
+ return 1
+
+ # Load old and new version of the package.
+ old_package = load_git(
+ against_path,
+ ref=against,
+ repo=repository,
+ extensions=loaded_extensions,
+ search_paths=search_paths,
+ allow_inspection=allow_inspection,
+ force_inspection=force_inspection,
+ resolve_aliases=True,
+ resolve_external=None,
+ )
+ if base_ref:
+ new_package = load_git(
+ package,
+ ref=base_ref,
+ repo=repository,
+ extensions=loaded_extensions,
+ search_paths=search_paths,
+ allow_inspection=allow_inspection,
+ force_inspection=force_inspection,
+ find_stubs_package=find_stubs_package,
+ resolve_aliases=True,
+ resolve_external=None,
+ )
+ else:
+ new_package = load(
+ package,
+ try_relative_path=True,
+ extensions=loaded_extensions,
+ search_paths=search_paths,
+ allow_inspection=allow_inspection,
+ force_inspection=force_inspection,
+ find_stubs_package=find_stubs_package,
+ resolve_aliases=True,
+ resolve_external=None,
+ )
+
+ # Find and display API breakages.
+ breakages = list(find_breaking_changes(old_package, new_package))
+
+ if color is None and (force_color := os.getenv("FORCE_COLOR", None)) is not None:
+ color = force_color.lower() in {"1", "true", "y", "yes", "on"}
+ colorama.deinit()
+ colorama.init(strip=color if color is None else not color)
+
+ if style is None:
+ style = ExplanationStyle.VERBOSE if verbose else ExplanationStyle.ONE_LINE
+ else:
+ style = ExplanationStyle(style)
+ for breakage in breakages:
+ print(breakage.explain(style=style), file=sys.stderr)
+
+ if breakages:
+ return 1
+ return 0
+
+
+def main(args: list[str] | None = None) -> int:
+ """Run the main program.
+
+ This function is executed when you type `griffe` or `python -m griffe`.
+
+ Parameters:
+ args: Arguments passed from the command line.
+
+ Returns:
+ An exit code.
+ """
+ # Parse arguments.
+ parser = get_parser()
+ opts: argparse.Namespace = parser.parse_args(args)
+ opts_dict = opts.__dict__
+ opts_dict.pop("debug_info")
+ subcommand = opts_dict.pop("subcommand")
+
+ # Initialize logging.
+ log_level = opts_dict.pop("log_level", DEFAULT_LOG_LEVEL)
+ try:
+ level = getattr(logging, log_level)
+ except AttributeError:
+ choices = "', '".join(_level_choices)
+ print(
+ f"griffe: error: invalid log level '{log_level}' (choose from '{choices}')",
+ file=sys.stderr,
+ )
+ return 1
+ else:
+ logging.basicConfig(format="%(levelname)-10s %(message)s", level=level)
+
+ # Increase maximum recursion limit to 2000.
+ sys.setrecursionlimit(max(2000, sys.getrecursionlimit()))
+
+ # Run subcommand.
+ commands: dict[str, Callable[..., int]] = {"check": check, "dump": dump}
+ return commands[subcommand](**opts_dict)
diff --git a/src/griffe/py.typed b/packages/griffe/src/griffe/_internal/py.typed
similarity index 100%
rename from src/griffe/py.typed
rename to packages/griffe/src/griffe/_internal/py.typed
diff --git a/packages/griffe/src/griffe/py.typed b/packages/griffe/src/griffe/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/griffelib/LICENSE b/packages/griffelib/LICENSE
new file mode 100644
index 00000000..8becbc45
--- /dev/null
+++ b/packages/griffelib/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2021, Timothée Mazzucotelli
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/packages/griffelib/README.md b/packages/griffelib/README.md
new file mode 100644
index 00000000..42bc0890
--- /dev/null
+++ b/packages/griffelib/README.md
@@ -0,0 +1,115 @@
+# Griffe (lib)
+
+[](https://github.com/mkdocstrings/griffe/actions?query=workflow%3Aci)
+[](https://mkdocstrings.github.io/griffe/)
+[](https://pypi.org/project/griffe/)
+[](https://app.gitter.im/#/room/#mkdocstrings_griffe:gitter.im)
+[](https://app.radicle.at/nodes/seed.radicle.at/rad:z4M5XTPDD4Wh1sm8iPCenF85J3z8Z)
+
+
+
+Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API.
+
+Griffe, pronounced "grif" (`/ɡʁif/`), is a french word that means "claw",
+but also "signature" in a familiar way. "On reconnaît bien là sa griffe."
+
+- [User guide](https://mkdocstrings.github.io/griffe/guide/users/)
+- [Contributor guide](https://mkdocstrings.github.io/griffe/guide/contributors/)
+- [API reference](https://mkdocstrings.github.io/griffe/reference/api/)
+
+## Installation
+
+```bash
+pip install griffe
+```
+
+With [`uv`](https://docs.astral.sh/uv/):
+
+```bash
+uv tool install griffe
+```
+
+## Usage
+
+### Dump JSON-serialized API
+
+**On the command line**, pass the names of packages to the `griffe dump` command:
+
+```console
+$ griffe dump httpx fastapi
+{
+ "httpx": {
+ "name": "httpx",
+ ...
+ },
+ "fastapi": {
+ "name": "fastapi",
+ ...
+ }
+}
+```
+
+See the [Serializing chapter](https://mkdocstrings.github.io/griffe/guide/users/serializing/) for more examples.
+
+### Check for API breaking changes
+
+Pass a relative path to the `griffe check` command:
+
+```console
+$ griffe check mypackage --verbose
+mypackage/mymodule.py:10: MyClass.mymethod(myparam):
+Parameter kind was changed:
+ Old: positional or keyword
+ New: keyword-only
+```
+
+For `src` layouts:
+
+```console
+$ griffe check --search src mypackage --verbose
+src/mypackage/mymodule.py:10: MyClass.mymethod(myparam):
+Parameter kind was changed:
+ Old: positional or keyword
+ New: keyword-only
+```
+
+It's also possible to directly **check packages from PyPI.org**
+(or other indexes configured through `PIP_INDEX_URL`).
+This feature is [available to sponsors only](https://mkdocstrings.github.io/griffe/insiders/)
+and requires that you install Griffe with the `pypi` extra:
+
+```bash
+pip install griffe[pypi]
+```
+
+The command syntax is:
+
+```bash
+griffe check package_name -b project-name==2.0 -a project-name==1.0
+```
+
+See the [Checking chapter](https://mkdocstrings.github.io/griffe/guide/users/checking/) for more examples.
+
+### Load and navigate data with Python
+
+**With Python**, loading a package:
+
+```python
+import griffe
+
+fastapi = griffe.load("fastapi")
+```
+
+Finding breaking changes:
+
+```python
+import griffe
+
+previous = griffe.load_git("mypackage", ref="0.2.0")
+current = griffe.load("mypackage")
+
+for breakage in griffe.find_breaking_changes(previous, current):
+ ...
+```
+
+See the [Loading chapter](https://mkdocstrings.github.io/griffe/guide/users/loading/) for more examples.
diff --git a/packages/griffelib/pyproject.toml b/packages/griffelib/pyproject.toml
new file mode 100644
index 00000000..e69de29b
diff --git a/src/griffe/__init__.py b/packages/griffelib/src/griffelib/__init__.py
similarity index 86%
rename from src/griffe/__init__.py
rename to packages/griffelib/src/griffelib/__init__.py
index 0f1f2e9a..b9108166 100644
--- a/src/griffe/__init__.py
+++ b/packages/griffelib/src/griffelib/__init__.py
@@ -2,7 +2,7 @@
# and exposes them as public objects. We have tests to make sure
# no object is forgotten in this list.
-"""Griffe package.
+"""Griffe package (library).
Signatures for entire Python programs.
Extract the structure, the frame, the skeleton of your project,
@@ -165,9 +165,9 @@
import warnings
from typing import Any
-from griffe._internal.agents.inspector import Inspector, inspect
-from griffe._internal.agents.nodes.assignments import get_instance_names, get_name, get_names
-from griffe._internal.agents.nodes.ast import (
+from griffelib._internal.agents.inspector import Inspector, inspect
+from griffelib._internal.agents.nodes.assignments import get_instance_names, get_name, get_names
+from griffelib._internal.agents.nodes.ast import (
ast_children,
ast_first_child,
ast_kind,
@@ -178,19 +178,19 @@
ast_previous_siblings,
ast_siblings,
)
-from griffe._internal.agents.nodes.docstrings import get_docstring
+from griffelib._internal.agents.nodes.docstrings import get_docstring
# YORE: Bump 2: Replace `ExportedName, ` with `` within line.
-from griffe._internal.agents.nodes.exports import ExportedName, get__all__, safe_get__all__
-from griffe._internal.agents.nodes.imports import relative_to_absolute
-from griffe._internal.agents.nodes.parameters import ParametersType, get_parameters
-from griffe._internal.agents.nodes.runtime import ObjectNode
-from griffe._internal.agents.nodes.values import get_value, safe_get_value
-from griffe._internal.agents.visitor import Visitor, builtin_decorators, stdlib_decorators, typing_overload, visit
-from griffe._internal.c3linear import c3linear_merge
-from griffe._internal.cli import DEFAULT_LOG_LEVEL, check, dump, get_parser, main
-from griffe._internal.collections import LinesCollection, ModulesCollection
-from griffe._internal.diff import (
+from griffelib._internal.agents.nodes.exports import ExportedName, get__all__, safe_get__all__
+from griffelib._internal.agents.nodes.imports import relative_to_absolute
+from griffelib._internal.agents.nodes.parameters import ParametersType, get_parameters
+from griffelib._internal.agents.nodes.runtime import ObjectNode
+from griffelib._internal.agents.nodes.values import get_value, safe_get_value
+from griffelib._internal.agents.visitor import Visitor, builtin_decorators, stdlib_decorators, typing_overload, visit
+from griffelib._internal.c3linear import c3linear_merge
+from griffelib._internal.cli import DEFAULT_LOG_LEVEL, check, dump, get_parser, main
+from griffelib._internal.collections import LinesCollection, ModulesCollection
+from griffelib._internal.diff import (
AttributeChangedTypeBreakage,
AttributeChangedValueBreakage,
Breakage,
@@ -206,8 +206,8 @@
ReturnChangedTypeBreakage,
find_breaking_changes,
)
-from griffe._internal.docstrings.google import GoogleOptions, parse_google
-from griffe._internal.docstrings.models import (
+from griffelib._internal.docstrings.google import GoogleOptions, parse_google
+from griffelib._internal.docstrings.models import (
DocstringAdmonition,
DocstringAttribute,
DocstringClass,
@@ -243,8 +243,8 @@
DocstringWarn,
DocstringYield,
)
-from griffe._internal.docstrings.numpy import NumpyOptions, parse_numpy
-from griffe._internal.docstrings.parsers import (
+from griffelib._internal.docstrings.numpy import NumpyOptions, parse_numpy
+from griffelib._internal.docstrings.parsers import (
DocstringDetectionMethod,
DocstringOptions,
DocstringStyle,
@@ -253,10 +253,10 @@
parse_auto,
parsers,
)
-from griffe._internal.docstrings.sphinx import SphinxOptions, parse_sphinx
-from griffe._internal.docstrings.utils import docstring_warning, parse_docstring_annotation
-from griffe._internal.encoders import JSONEncoder, json_decoder
-from griffe._internal.enumerations import (
+from griffelib._internal.docstrings.sphinx import SphinxOptions, parse_sphinx
+from griffelib._internal.docstrings.utils import docstring_warning, parse_docstring_annotation
+from griffelib._internal.encoders import JSONEncoder, json_decoder
+from griffelib._internal.enumerations import (
BreakageKind,
DocstringSectionKind,
ExplanationStyle,
@@ -267,7 +267,7 @@
Parser,
TypeParameterKind,
)
-from griffe._internal.exceptions import (
+from griffelib._internal.exceptions import (
AliasResolutionError,
BuiltinModuleError,
CyclicAliasError,
@@ -282,7 +282,7 @@
UnhandledEditableModuleError,
UnimportableModuleError,
)
-from griffe._internal.expressions import (
+from griffelib._internal.expressions import (
Expr,
ExprAttribute,
ExprBinOp,
@@ -324,28 +324,28 @@
safe_get_condition,
safe_get_expression,
)
-from griffe._internal.extensions.base import (
+from griffelib._internal.extensions.base import (
Extension,
Extensions,
LoadableExtensionType,
builtin_extensions,
load_extensions,
)
-from griffe._internal.extensions.dataclasses import DataclassesExtension
-from griffe._internal.finder import ModuleFinder, NamePartsAndPathType, NamePartsType, NamespacePackage, Package
-from griffe._internal.git import GitInfo, KnownGitService
-from griffe._internal.importer import dynamic_import, sys_path
-from griffe._internal.loader import GriffeLoader, load, load_git, load_pypi
-from griffe._internal.logger import Logger, get_logger, logger, patch_loggers
-from griffe._internal.merger import merge_stubs
-from griffe._internal.mixins import (
+from griffelib._internal.extensions.dataclasses import DataclassesExtension
+from griffelib._internal.finder import ModuleFinder, NamePartsAndPathType, NamePartsType, NamespacePackage, Package
+from griffelib._internal.git import GitInfo, KnownGitService
+from griffelib._internal.importer import dynamic_import, sys_path
+from griffelib._internal.loader import GriffeLoader, load, load_git, load_pypi
+from griffelib._internal.logger import Logger, get_logger, logger, patch_loggers
+from griffelib._internal.merger import merge_stubs
+from griffelib._internal.mixins import (
DelMembersMixin,
GetMembersMixin,
ObjectAliasMixin,
SerializationMixin,
SetMembersMixin,
)
-from griffe._internal.models import (
+from griffelib._internal.models import (
Alias,
Attribute,
Class,
@@ -360,8 +360,8 @@
TypeParameter,
TypeParameters,
)
-from griffe._internal.stats import Stats
-from griffe._internal.tests import (
+from griffelib._internal.stats import Stats
+from griffelib._internal.tests import (
TmpPackage,
htree,
module_vtree,
@@ -386,12 +386,12 @@
# YORE: Bump 2: Remove block.
def __getattr__(name: str) -> Any:
if name in _deprecated_names:
- from griffe._internal import git # noqa: PLC0415
+ from griffelib._internal import git # noqa: PLC0415
warnings.warn(
f"The `{name}` function is deprecated and will become unavailable in the next major version.",
DeprecationWarning,
- stacklevel=2,
+ stacklevel=3,
)
return getattr(git, f"_{name}")
diff --git a/src/griffe/_internal/__init__.py b/packages/griffelib/src/griffelib/_internal/__init__.py
similarity index 100%
rename from src/griffe/_internal/__init__.py
rename to packages/griffelib/src/griffelib/_internal/__init__.py
diff --git a/src/griffe/_internal/agents/__init__.py b/packages/griffelib/src/griffelib/_internal/agents/__init__.py
similarity index 100%
rename from src/griffe/_internal/agents/__init__.py
rename to packages/griffelib/src/griffelib/_internal/agents/__init__.py
diff --git a/src/griffe/_internal/agents/inspector.py b/packages/griffelib/src/griffelib/_internal/agents/inspector.py
similarity index 100%
rename from src/griffe/_internal/agents/inspector.py
rename to packages/griffelib/src/griffelib/_internal/agents/inspector.py
diff --git a/src/griffe/_internal/agents/nodes/__init__.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/__init__.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/__init__.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/__init__.py
diff --git a/src/griffe/_internal/agents/nodes/assignments.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/assignments.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/assignments.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/assignments.py
diff --git a/src/griffe/_internal/agents/nodes/ast.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/ast.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/ast.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/ast.py
diff --git a/src/griffe/_internal/agents/nodes/docstrings.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/docstrings.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/docstrings.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/docstrings.py
diff --git a/src/griffe/_internal/agents/nodes/exports.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/exports.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/exports.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/exports.py
diff --git a/src/griffe/_internal/agents/nodes/imports.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/imports.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/imports.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/imports.py
diff --git a/src/griffe/_internal/agents/nodes/parameters.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/parameters.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/parameters.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/parameters.py
diff --git a/src/griffe/_internal/agents/nodes/runtime.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/runtime.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/runtime.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/runtime.py
diff --git a/src/griffe/_internal/agents/nodes/values.py b/packages/griffelib/src/griffelib/_internal/agents/nodes/values.py
similarity index 100%
rename from src/griffe/_internal/agents/nodes/values.py
rename to packages/griffelib/src/griffelib/_internal/agents/nodes/values.py
diff --git a/src/griffe/_internal/agents/visitor.py b/packages/griffelib/src/griffelib/_internal/agents/visitor.py
similarity index 100%
rename from src/griffe/_internal/agents/visitor.py
rename to packages/griffelib/src/griffelib/_internal/agents/visitor.py
diff --git a/src/griffe/_internal/c3linear.py b/packages/griffelib/src/griffelib/_internal/c3linear.py
similarity index 100%
rename from src/griffe/_internal/c3linear.py
rename to packages/griffelib/src/griffelib/_internal/c3linear.py
diff --git a/src/griffe/_internal/cli.py b/packages/griffelib/src/griffelib/_internal/cli.py
similarity index 100%
rename from src/griffe/_internal/cli.py
rename to packages/griffelib/src/griffelib/_internal/cli.py
diff --git a/src/griffe/_internal/collections.py b/packages/griffelib/src/griffelib/_internal/collections.py
similarity index 100%
rename from src/griffe/_internal/collections.py
rename to packages/griffelib/src/griffelib/_internal/collections.py
diff --git a/src/griffe/_internal/debug.py b/packages/griffelib/src/griffelib/_internal/debug.py
similarity index 100%
rename from src/griffe/_internal/debug.py
rename to packages/griffelib/src/griffelib/_internal/debug.py
diff --git a/src/griffe/_internal/diff.py b/packages/griffelib/src/griffelib/_internal/diff.py
similarity index 100%
rename from src/griffe/_internal/diff.py
rename to packages/griffelib/src/griffelib/_internal/diff.py
diff --git a/src/griffe/_internal/docstrings/__init__.py b/packages/griffelib/src/griffelib/_internal/docstrings/__init__.py
similarity index 100%
rename from src/griffe/_internal/docstrings/__init__.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/__init__.py
diff --git a/src/griffe/_internal/docstrings/google.py b/packages/griffelib/src/griffelib/_internal/docstrings/google.py
similarity index 100%
rename from src/griffe/_internal/docstrings/google.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/google.py
diff --git a/src/griffe/_internal/docstrings/models.py b/packages/griffelib/src/griffelib/_internal/docstrings/models.py
similarity index 100%
rename from src/griffe/_internal/docstrings/models.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/models.py
diff --git a/src/griffe/_internal/docstrings/numpy.py b/packages/griffelib/src/griffelib/_internal/docstrings/numpy.py
similarity index 100%
rename from src/griffe/_internal/docstrings/numpy.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/numpy.py
diff --git a/src/griffe/_internal/docstrings/parsers.py b/packages/griffelib/src/griffelib/_internal/docstrings/parsers.py
similarity index 100%
rename from src/griffe/_internal/docstrings/parsers.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/parsers.py
diff --git a/src/griffe/_internal/docstrings/sphinx.py b/packages/griffelib/src/griffelib/_internal/docstrings/sphinx.py
similarity index 100%
rename from src/griffe/_internal/docstrings/sphinx.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/sphinx.py
diff --git a/src/griffe/_internal/docstrings/utils.py b/packages/griffelib/src/griffelib/_internal/docstrings/utils.py
similarity index 100%
rename from src/griffe/_internal/docstrings/utils.py
rename to packages/griffelib/src/griffelib/_internal/docstrings/utils.py
diff --git a/src/griffe/_internal/encoders.py b/packages/griffelib/src/griffelib/_internal/encoders.py
similarity index 100%
rename from src/griffe/_internal/encoders.py
rename to packages/griffelib/src/griffelib/_internal/encoders.py
diff --git a/src/griffe/_internal/enumerations.py b/packages/griffelib/src/griffelib/_internal/enumerations.py
similarity index 100%
rename from src/griffe/_internal/enumerations.py
rename to packages/griffelib/src/griffelib/_internal/enumerations.py
diff --git a/src/griffe/_internal/exceptions.py b/packages/griffelib/src/griffelib/_internal/exceptions.py
similarity index 100%
rename from src/griffe/_internal/exceptions.py
rename to packages/griffelib/src/griffelib/_internal/exceptions.py
diff --git a/src/griffe/_internal/expressions.py b/packages/griffelib/src/griffelib/_internal/expressions.py
similarity index 100%
rename from src/griffe/_internal/expressions.py
rename to packages/griffelib/src/griffelib/_internal/expressions.py
diff --git a/src/griffe/_internal/extensions/__init__.py b/packages/griffelib/src/griffelib/_internal/extensions/__init__.py
similarity index 100%
rename from src/griffe/_internal/extensions/__init__.py
rename to packages/griffelib/src/griffelib/_internal/extensions/__init__.py
diff --git a/src/griffe/_internal/extensions/base.py b/packages/griffelib/src/griffelib/_internal/extensions/base.py
similarity index 100%
rename from src/griffe/_internal/extensions/base.py
rename to packages/griffelib/src/griffelib/_internal/extensions/base.py
diff --git a/src/griffe/_internal/extensions/dataclasses.py b/packages/griffelib/src/griffelib/_internal/extensions/dataclasses.py
similarity index 100%
rename from src/griffe/_internal/extensions/dataclasses.py
rename to packages/griffelib/src/griffelib/_internal/extensions/dataclasses.py
diff --git a/src/griffe/_internal/finder.py b/packages/griffelib/src/griffelib/_internal/finder.py
similarity index 100%
rename from src/griffe/_internal/finder.py
rename to packages/griffelib/src/griffelib/_internal/finder.py
diff --git a/src/griffe/_internal/git.py b/packages/griffelib/src/griffelib/_internal/git.py
similarity index 100%
rename from src/griffe/_internal/git.py
rename to packages/griffelib/src/griffelib/_internal/git.py
diff --git a/src/griffe/_internal/importer.py b/packages/griffelib/src/griffelib/_internal/importer.py
similarity index 100%
rename from src/griffe/_internal/importer.py
rename to packages/griffelib/src/griffelib/_internal/importer.py
diff --git a/src/griffe/_internal/loader.py b/packages/griffelib/src/griffelib/_internal/loader.py
similarity index 100%
rename from src/griffe/_internal/loader.py
rename to packages/griffelib/src/griffelib/_internal/loader.py
diff --git a/src/griffe/_internal/logger.py b/packages/griffelib/src/griffelib/_internal/logger.py
similarity index 100%
rename from src/griffe/_internal/logger.py
rename to packages/griffelib/src/griffelib/_internal/logger.py
diff --git a/src/griffe/_internal/merger.py b/packages/griffelib/src/griffelib/_internal/merger.py
similarity index 100%
rename from src/griffe/_internal/merger.py
rename to packages/griffelib/src/griffelib/_internal/merger.py
diff --git a/src/griffe/_internal/mixins.py b/packages/griffelib/src/griffelib/_internal/mixins.py
similarity index 100%
rename from src/griffe/_internal/mixins.py
rename to packages/griffelib/src/griffelib/_internal/mixins.py
diff --git a/src/griffe/_internal/models.py b/packages/griffelib/src/griffelib/_internal/models.py
similarity index 100%
rename from src/griffe/_internal/models.py
rename to packages/griffelib/src/griffelib/_internal/models.py
diff --git a/packages/griffelib/src/griffelib/_internal/py.typed b/packages/griffelib/src/griffelib/_internal/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/src/griffe/_internal/stats.py b/packages/griffelib/src/griffelib/_internal/stats.py
similarity index 100%
rename from src/griffe/_internal/stats.py
rename to packages/griffelib/src/griffelib/_internal/stats.py
diff --git a/src/griffe/_internal/tests.py b/packages/griffelib/src/griffelib/_internal/tests.py
similarity index 100%
rename from src/griffe/_internal/tests.py
rename to packages/griffelib/src/griffelib/_internal/tests.py
diff --git a/packages/griffelib/src/griffelib/py.typed b/packages/griffelib/src/griffelib/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/helpers.py b/tests/helpers.py
index 47613ddd..1ca06eae 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -6,7 +6,7 @@
import sys
from tempfile import gettempdir
-from griffe._internal.tests import _TMPDIR_PREFIX
+from griffelib._internal.tests import _TMPDIR_PREFIX
def clear_sys_modules(name: str | None = None) -> None:
diff --git a/tests/test_encoders.py b/tests/test_encoders.py
index e6e6debb..3ce6f657 100644
--- a/tests/test_encoders.py
+++ b/tests/test_encoders.py
@@ -8,7 +8,7 @@
import pytest
from jsonschema import ValidationError, validate
-from griffe import Attribute, Class, Function, GriffeLoader, Kind, Module, Object, temporary_visited_module
+from griffelib import Attribute, Class, Function, GriffeLoader, Kind, Module, Object, temporary_visited_module
def test_minimal_data_is_enough() -> None: