<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.dura-lex.org/index.php?action=history&amp;feed=atom&amp;title=Development%2FPackaging</id>
	<title>Development/Packaging - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.dura-lex.org/index.php?action=history&amp;feed=atom&amp;title=Development%2FPackaging"/>
	<link rel="alternate" type="text/html" href="https://wiki.dura-lex.org/index.php?title=Development/Packaging&amp;action=history"/>
	<updated>2026-04-23T05:44:26Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://wiki.dura-lex.org/index.php?title=Development/Packaging&amp;diff=41&amp;oldid=prev</id>
		<title>Nicolas: Create Packaging conventions page from coding-conventions/PACKAGING.md (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://wiki.dura-lex.org/index.php?title=Development/Packaging&amp;diff=41&amp;oldid=prev"/>
		<updated>2026-04-23T02:05:32Z</updated>

		<summary type="html">&lt;p&gt;Create Packaging conventions page from coding-conventions/PACKAGING.md (via create-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Packaging Conventions =&lt;br /&gt;
&lt;br /&gt;
All duralex-* packages follow these conventions for consistent packaging, installation, and namespace management.&lt;br /&gt;
&lt;br /&gt;
== Naming ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Layer !! Convention !! Example&lt;br /&gt;
|-&lt;br /&gt;
| Brand || Dura Lex (always two words) || —&lt;br /&gt;
|-&lt;br /&gt;
| Domain || &amp;lt;code&amp;gt;dura-lex.*&amp;lt;/code&amp;gt; (hyphenated) || &amp;lt;code&amp;gt;dura-lex.org&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| GitHub org || &amp;lt;code&amp;gt;duralex&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;github.com/duralex/&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| PyPI package || &amp;lt;code&amp;gt;duralex-{name}&amp;lt;/code&amp;gt; (hyphenated) || &amp;lt;code&amp;gt;duralex-fr&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;duralex-mcp&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Python import || &amp;lt;code&amp;gt;duralex.{name}&amp;lt;/code&amp;gt; (dotted) || &amp;lt;code&amp;gt;from duralex.corpus import ...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Exception || The core package is just &amp;lt;code&amp;gt;duralex&amp;lt;/code&amp;gt; on PyPI || &amp;lt;code&amp;gt;pip install duralex&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Repository structure (6 repos) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
~/duralex/&lt;br /&gt;
├── duralex/                          # PyPI: duralex (core + corpus + specs)&lt;br /&gt;
│   ├── pyproject.toml&lt;br /&gt;
│   ├── src/duralex/&lt;br /&gt;
│   │   ├── corpus/                   # DocumentStore, FTS, TagQuery&lt;br /&gt;
│   │   ├── temporal/                 # versioning, temporal types&lt;br /&gt;
│   │   ├── data/                     # citation types, text utils&lt;br /&gt;
│   │   ├── annotations/             # annotation framework&lt;br /&gt;
│   │   ├── concepts/                # concept definitions&lt;br /&gt;
│   │   ├── errors.py                # DuralexError base&lt;br /&gt;
│   │   ├── database.py              # DSN builder&lt;br /&gt;
│   │   ├── logging.py               # JSON/console logging&lt;br /&gt;
│   │   ├── middleware.py             # ASGI middleware&lt;br /&gt;
│   │   └── retry.py                 # exponential backoff&lt;br /&gt;
│   ├── tests/&lt;br /&gt;
│   ├── sql/                          # corpus DDL&lt;br /&gt;
│   ├── spec/                         # authoritative specifications&lt;br /&gt;
│   ├── coding-conventions/           # this directory&lt;br /&gt;
│   ├── design-decisions/             # ADRs&lt;br /&gt;
│   ├── research/                     # investigation logs&lt;br /&gt;
│   └── issues/                       # issue tracker&lt;br /&gt;
│&lt;br /&gt;
├── duralex-jurisdictions/            # monorepo, two PyPI packages&lt;br /&gt;
│   ├── duralex-fr/                   # PyPI: duralex-fr&lt;br /&gt;
│   │   ├── pyproject.toml&lt;br /&gt;
│   │   ├── src/duralex/fr/&lt;br /&gt;
│   │   └── tests/&lt;br /&gt;
│   └── duralex-eu/                   # PyPI: duralex-eu&lt;br /&gt;
│       ├── pyproject.toml&lt;br /&gt;
│       ├── src/duralex/eu/&lt;br /&gt;
│       └── tests/&lt;br /&gt;
│&lt;br /&gt;
├── duralex-ingest/                   # monorepo, three PyPI packages&lt;br /&gt;
│   ├── duralex-ingest/               # PyPI: duralex-ingest&lt;br /&gt;
│   │   ├── pyproject.toml&lt;br /&gt;
│   │   ├── src/duralex/ingest/       # NO __init__.py (namespace)&lt;br /&gt;
│   │   └── tests/&lt;br /&gt;
│   ├── duralex-ingest-fr/            # PyPI: duralex-ingest-fr&lt;br /&gt;
│   │   ├── pyproject.toml&lt;br /&gt;
│   │   ├── src/duralex/ingest/fr/&lt;br /&gt;
│   │   └── tests/&lt;br /&gt;
│   └── duralex-ingest-eu/            # PyPI: duralex-ingest-eu&lt;br /&gt;
│       ├── pyproject.toml&lt;br /&gt;
│       ├── src/duralex/ingest/eu/&lt;br /&gt;
│       └── tests/&lt;br /&gt;
│&lt;br /&gt;
├── duralex-mcp/                      # PyPI: duralex-mcp (+ Docker infra)&lt;br /&gt;
│   ├── pyproject.toml&lt;br /&gt;
│   ├── src/duralex/mcp/&lt;br /&gt;
│   ├── tests/&lt;br /&gt;
│   ├── Dockerfile&lt;br /&gt;
│   ├── docker-compose.dev.yml&lt;br /&gt;
│   ├── docker-compose.prod.yml&lt;br /&gt;
│   └── Caddyfile&lt;br /&gt;
│&lt;br /&gt;
├── duralex-portal/                   # PyPI: duralex-portal&lt;br /&gt;
│   ├── pyproject.toml&lt;br /&gt;
│   ├── src/duralex/portal/&lt;br /&gt;
│   └── tests/&lt;br /&gt;
│&lt;br /&gt;
└── duralex-feedback/                 # standalone, not distributed&lt;br /&gt;
    ├── server.py&lt;br /&gt;
    ├── Dockerfile&lt;br /&gt;
    └── docker-compose.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== src layout with namespace packages ==&lt;br /&gt;
&lt;br /&gt;
Every package uses the &amp;#039;&amp;#039;&amp;#039;src layout&amp;#039;&amp;#039;&amp;#039; with implicit namespace packages (PEP 420).&lt;br /&gt;
&lt;br /&gt;
The critical rule: &amp;#039;&amp;#039;&amp;#039;no &amp;lt;code&amp;gt;__init__.py&amp;lt;/code&amp;gt; in namespace directories&amp;#039;&amp;#039;&amp;#039;. Only leaf packages have &amp;lt;code&amp;gt;__init__.py&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
duralex-fr/&lt;br /&gt;
├── pyproject.toml&lt;br /&gt;
├── src/&lt;br /&gt;
│   └── duralex/                     # NO __init__.py (namespace)&lt;br /&gt;
│       └── fr/                      # NO __init__.py (namespace)&lt;br /&gt;
│           ├── mcp/&lt;br /&gt;
│           │   ├── __init__.py      # leaf package&lt;br /&gt;
│           │   └── ...&lt;br /&gt;
│           ├── refs/&lt;br /&gt;
│           │   ├── __init__.py&lt;br /&gt;
│           │   └── ...&lt;br /&gt;
│           └── courts/&lt;br /&gt;
│               ├── __init__.py&lt;br /&gt;
│               └── ...&lt;br /&gt;
└── tests/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Special case: duralex-ingest namespace ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;duralex.ingest&amp;lt;/code&amp;gt; namespace is shared across three packages. The base &amp;lt;code&amp;gt;duralex-ingest&amp;lt;/code&amp;gt; package must NOT have &amp;lt;code&amp;gt;__init__.py&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;src/duralex/ingest/&amp;lt;/code&amp;gt; — otherwise Python blocks namespace discovery of &amp;lt;code&amp;gt;duralex.ingest.fr&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;duralex.ingest.eu&amp;lt;/code&amp;gt; from sibling packages.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
duralex-ingest/duralex-ingest/src/duralex/ingest/       # NO __init__.py&lt;br /&gt;
duralex-ingest/duralex-ingest/src/duralex/ingest/database/__init__.py   # leaf&lt;br /&gt;
duralex-ingest/duralex-ingest-fr/src/duralex/ingest/fr/__init__.py      # leaf&lt;br /&gt;
duralex-ingest/duralex-ingest-eu/src/duralex/ingest/eu/__init__.py      # leaf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== pyproject.toml template ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;toml&amp;quot;&amp;gt;&lt;br /&gt;
[build-system]&lt;br /&gt;
requires = [&amp;quot;setuptools&amp;gt;=69.0&amp;quot;]&lt;br /&gt;
build-backend = &amp;quot;setuptools.build_meta&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[project]&lt;br /&gt;
name = &amp;quot;duralex-fr&amp;quot;&lt;br /&gt;
version = &amp;quot;0.1.0&amp;quot;&lt;br /&gt;
description = &amp;quot;France: legal reference resolver, court hierarchy, and validation for the Dura Lex ecosystem&amp;quot;&lt;br /&gt;
requires-python = &amp;quot;&amp;gt;=3.12&amp;quot;&lt;br /&gt;
license = &amp;quot;MIT&amp;quot;&lt;br /&gt;
authors = [{ name = &amp;quot;Nicolas Baldeck&amp;quot; }]&lt;br /&gt;
keywords = [&amp;quot;legal&amp;quot;, &amp;quot;open-data&amp;quot;, &amp;quot;france&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
dependencies = [&lt;br /&gt;
    &amp;quot;duralex&amp;gt;=0.1.0&amp;quot;,&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
[project.optional-dependencies]&lt;br /&gt;
dev = [&lt;br /&gt;
    &amp;quot;pytest&amp;gt;=8.2&amp;quot;,&lt;br /&gt;
    &amp;quot;ruff&amp;gt;=0.15&amp;quot;,&lt;br /&gt;
    &amp;quot;mypy&amp;gt;=1.15&amp;quot;,&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
[tool.setuptools.packages.find]&lt;br /&gt;
where = [&amp;quot;src&amp;quot;]&lt;br /&gt;
include = [&amp;quot;duralex.fr*&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[tool.ruff]&lt;br /&gt;
line-length = 120&lt;br /&gt;
target-version = &amp;quot;py312&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[tool.ruff.lint]&lt;br /&gt;
select = [&amp;quot;E&amp;quot;, &amp;quot;F&amp;quot;, &amp;quot;W&amp;quot;, &amp;quot;I&amp;quot;, &amp;quot;UP&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;SIM&amp;quot;, &amp;quot;TCH&amp;quot;, &amp;quot;RUF&amp;quot;, &amp;quot;PTH&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[tool.ruff.format]&lt;br /&gt;
quote-style = &amp;quot;double&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[tool.mypy]&lt;br /&gt;
python_version = &amp;quot;3.12&amp;quot;&lt;br /&gt;
strict = true&lt;br /&gt;
namespace_packages = true&lt;br /&gt;
explicit_package_bases = true&lt;br /&gt;
mypy_path = [&amp;quot;src&amp;quot;, &amp;quot;../../duralex/src&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
[tool.pytest.ini_options]&lt;br /&gt;
testpaths = [&amp;quot;tests&amp;quot;]&lt;br /&gt;
addopts = [&amp;quot;--strict-config&amp;quot;, &amp;quot;--strict-markers&amp;quot;, &amp;quot;--import-mode=importlib&amp;quot;, &amp;quot;-m not slow and not integration&amp;quot;]&lt;br /&gt;
xfail_strict = true&lt;br /&gt;
consider_namespace_packages = true&lt;br /&gt;
filterwarnings = [&amp;quot;error&amp;quot;]&lt;br /&gt;
markers = [&lt;br /&gt;
    &amp;quot;slow: tests with extended runtime&amp;quot;,&lt;br /&gt;
    &amp;quot;integration: requires PostgreSQL (testcontainers)&amp;quot;,&lt;br /&gt;
]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Editable development installs ==&lt;br /&gt;
&lt;br /&gt;
Install all packages in editable mode from the repo root:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd ~/duralex&lt;br /&gt;
pip install -e &amp;quot;./duralex[dev,pg]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-jurisdictions/duralex-fr[dev,db]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-jurisdictions/duralex-eu[dev]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-ingest/duralex-ingest[dev]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-ingest/duralex-ingest-fr[dev]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-ingest/duralex-ingest-eu[dev]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-mcp[dev]&amp;quot;&lt;br /&gt;
pip install -e &amp;quot;./duralex-portal[dev]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If namespace resolution breaks between sibling packages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -e &amp;quot;.[dev]&amp;quot; --config-settings editable_mode=compat&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Version policy ==&lt;br /&gt;
&lt;br /&gt;
All packages start at &amp;lt;code&amp;gt;0.1.0&amp;lt;/code&amp;gt;. Packages are versioned independently. A major version bump in &amp;lt;code&amp;gt;duralex&amp;lt;/code&amp;gt; triggers compatibility review in all downstream packages.&lt;br /&gt;
&lt;br /&gt;
== Brand vs. code ==&lt;br /&gt;
&lt;br /&gt;
The brand is always written &amp;#039;&amp;#039;&amp;#039;Dura Lex&amp;#039;&amp;#039;&amp;#039; (two words). Code identifiers use &amp;lt;code&amp;gt;duralex&amp;lt;/code&amp;gt; (one word, no space) because Python and PyPI do not allow spaces. Never write &amp;quot;Duralex&amp;quot; — it is either &amp;quot;Dura Lex&amp;quot; (prose) or &amp;lt;code&amp;gt;duralex&amp;lt;/code&amp;gt; (code).&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Nicolas</name></author>
	</entry>
</feed>