Skip to content

Commit 8ee867e

Browse files
authored
Merge pull request #2 from pydantify/feature/cicd
Add main github workflow
2 parents b029872 + 7c684e8 commit 8ee867e

File tree

10 files changed

+407
-9
lines changed

10 files changed

+407
-9
lines changed

.github/workflows/main.yaml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
name: test pydantify_common
3+
on: [push, pull_request]
4+
5+
jobs:
6+
linters:
7+
name: linters
8+
strategy:
9+
matrix:
10+
python-version: ["3.13"]
11+
platform: [ubuntu-latest]
12+
runs-on: ${{ matrix.platform }}
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install uv and set the python version
17+
uses: astral-sh/setup-uv@v6
18+
with:
19+
enable-cache: true
20+
python-version: ${{ matrix.python-version }}
21+
22+
- name: Install the project
23+
run: uv sync --locked --all-extras --dev
24+
25+
- name: Run ruff
26+
run: make ruff
27+
- name: Run mypy
28+
run: make mypy
29+
30+
pytest:
31+
name: Testing on Python ${{ matrix.python-version }} (${{ matrix.platform}})
32+
defaults:
33+
run:
34+
shell: bash
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
python-version: ["3.10", "3.11", "3.12", "3.13"]
39+
# platform: [ubuntu-latest, macOS-latest, windows-latest]
40+
platform: [ubuntu-latest, macOS-latest]
41+
runs-on: ${{ matrix.platform }}
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Install uv and set the python version
46+
uses: astral-sh/setup-uv@v6
47+
with:
48+
enable-cache: true
49+
python-version: ${{ matrix.python-version }}
50+
51+
- name: Install the project
52+
run: uv sync --locked --all-extras --dev
53+
54+
- name: Run pytest
55+
run: make pytest
56+
57+
release:
58+
name: Releasing to pypi
59+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
60+
needs: [linters, pytest]
61+
runs-on: ubuntu-latest
62+
steps:
63+
- uses: actions/checkout@v2
64+
65+
- name: Install uv and set the python version
66+
uses: astral-sh/setup-uv@v6
67+
with:
68+
enable-cache: true
69+
python-version: "3.13"
70+
71+
- name: build release
72+
run: uv build
73+
74+
- name: Publish package to PyPI
75+
uses: pypa/gh-action-pypi-publish@release/v1
76+
with:
77+
user: __token__
78+
password: ${{ secrets.PYPI_API_TOKEN }}
79+
80+
- uses: actions/upload-artifact@v5
81+
with:
82+
name: build
83+
path: dist/*

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
PROJECT=pydantify_common
2+
CODE_DIRS=src/${PROJECT} tests
3+
4+
# Run pytest
5+
.PHONY: pytest
6+
pytest:
7+
uv run pytest -vs ${ARGS}
8+
9+
# Check if the python code needs to be reformatted
10+
.PHONY: ruff
11+
black:
12+
uv run ruff format --check ${CODE_DIRS}
13+
14+
# Python type check
15+
.PHONY: mypy
16+
mypy:
17+
uv run mypy src/${PROJECT}
18+
19+
# Runn pytest, black and mypy
20+
.PHONY: tests
21+
tests: pytest black mypy

README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# pydantify-common
2+
3+
A Python library providing common base classes and utilities for building Pydantic-based models with XML serialization support. This package serves as the foundation for the pydantify project, enabling seamless conversion between Pydantic models and XML representations.
4+
5+
## Features
6+
7+
- **Base Model Classes**: `PydantifyModel` and `XMLPydantifyModel` for creating structured data models
8+
- **XML Serialization**: Convert Pydantic models to XML with namespace support
9+
- **NETCONF Compatibility**: Built-in support for NETCONF data root elements
10+
- **Type Safety**: Full type hints and Pydantic v2 support
11+
- **Flexible Output**: Pretty-print and custom root element options
12+
13+
## Installation
14+
15+
```bash
16+
pip install pydantify-common
17+
```
18+
19+
## Requirements
20+
21+
- Python >= 3.10
22+
- pydantic >= 2
23+
- lxml >= 6.0.2
24+
25+
## Quick Start
26+
27+
### Basic Model Definition
28+
29+
Make sure the pydantic model inherits from `XMLPydantifyModel` and defines `namespace` and `prefix` as class variables.
30+
31+
```python
32+
from pydantify_common.model import XMLPydantifyModel
33+
34+
class MyModel(XMLPydantifyModel):
35+
namespace = "http://example.com/schema"
36+
prefix = "ex"
37+
38+
name: str
39+
value: int
40+
```
41+
42+
### XML Serialization
43+
44+
```python
45+
from pydantify_common.helper import model_dump_xml_string
46+
47+
model = MyModel(name="test", value=42)
48+
xml_str = model_dump_xml_string(model, pretty_print=True)
49+
print(xml_str)
50+
```
51+
52+
### NETCONF Data Root
53+
54+
```python
55+
# Add NETCONF-compatible data root element
56+
xml_str = model_dump_xml_string(model, pretty_print=True, data_root=True)
57+
```
58+
59+
## API Reference
60+
61+
### PydantifyModel
62+
63+
Base class for all pydantify models. Inherits from Pydantic's `BaseModel`.
64+
65+
```python
66+
class PydantifyModel(BaseModel):
67+
pass
68+
```
69+
70+
### XMLPydantifyModel
71+
72+
Extended base class with XML serialization capabilities.
73+
74+
**Class Variables:**
75+
- `namespace` (str): XML namespace URI
76+
- `prefix` (str): XML namespace prefix
77+
78+
**Methods:**
79+
- `model_dump_xml() -> Element`: Returns an lxml Element representation
80+
- `fields_to_elements(container_name: str | None = None) -> Element`: Converts model fields to XML elements
81+
82+
### model_dump_xml_string()
83+
84+
Helper function to serialize XMLPydantifyModel instances to XML strings.
85+
86+
```python
87+
def model_dump_xml_string(
88+
model: XMLPydantifyModel,
89+
*,
90+
pretty_print: bool = False,
91+
data_root: bool = False
92+
) -> str
93+
```
94+
95+
**Parameters:**
96+
- `model`: The XMLPydantifyModel instance to serialize
97+
- `pretty_print`: Enable formatted XML output (default: False)
98+
- `data_root`: Add NETCONF-compatible `<data>` root element (default: False)
99+
100+
**Returns:** XML string representation of the model
101+
102+
## Examples
103+
104+
The project includes comprehensive examples in the `tests/examples/` directory:
105+
106+
- **with_augment**: Demonstrates augmented YANG models with namespace support
107+
- **with_import_uses**: Shows handling of YANG imports and type reuse
108+
109+
Run tests to see examples in action:
110+
111+
```bash
112+
uv run pytest tests/test_examples.py -vvv
113+
```
114+
115+
## Development
116+
117+
### Setup Development Environment
118+
119+
```bash
120+
uv sync
121+
```
122+
123+
### Running Tests
124+
125+
```bash
126+
make tests
127+
```
128+
129+
### Code Quality
130+
131+
```bash
132+
# Type checking
133+
make mypy
134+
135+
# Linting and formatting
136+
make ruff
137+
```
138+
139+
## Contributing
140+
141+
Contributions are welcome! Please ensure all tests pass and code follows the project's style guidelines before submitting a pull request.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ build-backend = "hatchling.build"
1818

1919
[dependency-groups]
2020
dev = [
21+
"lxml-stubs>=0.5.1",
22+
"mypy>=1.19.1",
2123
"pytest>=9.0.2",
2224
"rich>=14.2.0",
2325
"ruff>=0.11.12",

src/pydantify_common/helper.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from lxml import etree
22
from .model import XMLPydantifyModel
3-
from typing import Any
43

54

65
def model_dump_xml_string(
@@ -11,7 +10,7 @@ def model_dump_xml_string(
1110
# Add `<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">` root element
1211
root = etree.Element(
1312
"data",
14-
nsmap={None: "urn:ietf:params:xml:ns:netconf:base:1.0"},
13+
nsmap={None: "urn:ietf:params:xml:ns:netconf:base:1.0"}, # type: ignore
1514
)
1615
root.append(data)
1716
data = root

src/pydantify_common/model.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
from typing import ClassVar
1+
from typing import ClassVar, TYPE_CHECKING
22

33
from pydantic import BaseModel
44
from lxml import etree
55

6+
if TYPE_CHECKING:
7+
from lxml.etree import _Element
8+
69

710
class PydantifyModel(BaseModel):
811
pass
@@ -12,18 +15,18 @@ class XMLPydantifyModel(PydantifyModel):
1215
namespace: ClassVar[str]
1316
prefix: ClassVar[str]
1417

15-
def model_dump_xml(self) -> etree.Element:
18+
def model_dump_xml(self) -> "_Element":
1619
data = self.fields_to_elements()
1720
return list(data)[0]
1821

1922
def fields_to_elements(
2023
self,
2124
container_name: str | None = None,
22-
) -> list[etree.Element]:
25+
) -> "_Element":
2326
if container_name:
2427
root = etree.Element(
2528
container_name,
26-
nsmap={None: self.namespace},
29+
nsmap={None: self.namespace}, # type: ignore
2730
)
2831
else:
2932
root = etree.Element("root")

tests/examples/with_augment/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Annotated, List, ClassVar
44

5-
from pydantic import ConfigDict, Field, RootModel
5+
from pydantic import ConfigDict, Field
66
from pydantify_common.model import XMLPydantifyModel
77

88

tests/examples/with_import_uses/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Annotated, List, ClassVar
44

5-
from pydantic import ConfigDict, Field, RootModel
5+
from pydantic import ConfigDict, Field
66
from pydantify_common.model import XMLPydantifyModel
77

88

tests/test_examples.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from pathlib import Path
2-
from pydantify_common.model import XMLPydantifyModel
32
from pydantify_common.helper import model_dump_xml_string
43
from lxml import etree
54

0 commit comments

Comments
 (0)