Merge pull request 'Upgrade to py-allspice 3.0.0' () from jt/upgrade-py-allspice into main

Reviewed-on: 
Reviewed-by: shrikanth-allspice <shrikanth-allspice@noreply.hub.allspice.io>
This commit is contained in:
Jon Tran 2024-05-10 20:12:48 +00:00
commit 694b7e5ccf
4 changed files with 120 additions and 62 deletions

View File

@ -2,6 +2,6 @@ FROM python:3.12-bookworm
COPY entrypoint.py /entrypoint.py
RUN pip install py-allspice~=2.5
RUN pip install py-allspice~=3.0
ENTRYPOINT [ "/entrypoint.py" ]

View File

@ -1,25 +1,71 @@
# Generate BOM for Altium Projects
Generate a BOM for an Altium project on AllSpice Hub using py-allspice. This
currently uses the PCB file for computing quantities.
Generate a BOM for an Altium project on AllSpice Hub using py-allspice.
## Usage
Add the following step to your actions:
Add the following steps to your actions:
```yaml
# Checkout is only needed if columns.json is committed in the repo.
- name: Checkout
uses: actions/checkout@v3
- name: Generate BOM
uses: https://hub.allspice.io/Actions/generate-bom-altium@main
with:
project_path: Archimajor.PrjPcb
pcb_path: Archimajor.PcbDoc
columns: .allspice/columns.json
output_file_name: bom.csv
attributes_mapping: '
{
"description": ["PART DESCRIPTION"],
"designator": ["Designator"],
"manufacturer": ["Manufacturer", "MANUFACTURER"],
"part_number": ["PART", "MANUFACTURER #"]
}
'
```
where `.allspice/columns.json` looks like:
```json
{
"part_number": ["PART", "MANUFACTURER #", "MPN"],
"manufacturer": ["Manufacturer", "MANUFACTURER", "MFG", "Mfg"],
"designator": ["Designator", "REFDES", "Refdes", "Ref"],
"part_id": ["_part_id"],
"description": ["PART DESCRIPTION", "_description"]
}
```
### Customizing the Attributes Extracted by the BOM Script
This script relies on a `columns.json` file. This file maps the Component
Attributes in the SchDoc files to the columns of the BOM. An example for
`columns.json` is:
```json
{
"description": ["PART DESCRIPTION"],
"designator": ["Designator"],
"manufacturer": ["Manufacturer", "MANUFACTURER"],
"part_number": ["PART", "MANUFACTURER #"]
}
```
In this file, the keys are the names of the columns in the BOM, and the values
are a list of the names of the attributes in the SchDoc files that should be
mapped to that column. For example, if your part number is stored either in the
`PART` or `MANUFACTURER #` attribute, you would add both of those to the list.
If there is only one attribute, you can omit the list and just use a string. The
script checks these attributes in order, and uses the _first_ one it finds. So
if both `PART` and `MANUFACTURER #` are defined, it will use `PART`.
Note that py-allspice also adds two attributes: `_part_id` and `_description`.
These correspond to the Library Reference and description fields of the
component. The underscore is added ahead of the name to prevent these additional
attributes from overriding any of your own. You can use these like:
```json
{
"Description": ["PART DESCRIPTION", "_description"],
"Part Number": ["PART", "_part_id"]
}
```
By default, the script picks up a `columns.json` file from the working
directory. If you want to keep it in a different place, or rename it, you can
pass the `--columns` argument to the script to specify where it is.

View File

@ -7,16 +7,24 @@ inputs:
project_path:
description: "Path to the project file from the root of the repo"
required: true
pcb_path:
description: "Path to the PCB file from the root of the repo"
required: true
output_file_name:
description: "Name of the output file"
required: true
default: "bom.csv"
attributes_mapping:
description: "JSON string with the mapping of the attributes to the AllSpice attributes"
columns:
description: >
A path to a JSON file mapping columns to the attributes they are from.
required: true
group_by:
description: >
A comma-separated list of columns to group the BOM by. If not present, the
BOM will be flat.
default: ''
variant:
description: >
The variant of the project to generate the BOM for. If not present, the
BOM will be generated for the default variant.
default: ''
runs:
using: "docker"
image: "Dockerfile"
@ -25,12 +33,15 @@ runs:
- ${{ github.sha }}
- "--allspice_hub_url"
- ${{ github.server_url }}
- "--attributes_mapping"
- ${{ inputs.attributes_mapping }}
- "--columns"
- ${{ inputs.columns }}
- "--group_by"
- ${{ inputs.group_by }}
- "--variant"
- ${{ inputs.variant }}
- "--output_file"
- "${{ github.workspace}}/${{ inputs.output_file_name }}"
- ${{ github.repository }}
- ${{ inputs.project_path }}
- ${{ inputs.pcb_path }}
env:
ALLSPICE_AUTH_TOKEN: ${{ github.token }}

View File

@ -11,7 +11,8 @@ import sys
from contextlib import ExitStack
from allspice import AllSpice
from allspice.utils.bom_generation import AttributesMapping, generate_bom_for_altium
from allspice.utils.bom_generation import generate_bom_for_altium
if __name__ == "__main__":
# Parse command line arguments. If you're writing a special purpose script,
@ -19,36 +20,55 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="generate_bom", description="Generate a BOM from a PrjPcb file."
)
parser.add_argument("repository", help="The repo containing the project")
parser.add_argument(
"prjpcb_file", help="The path to the PrjPcb file in the source repo."
"repository", help="The repo containing the project in the form 'owner/repo'"
)
parser.add_argument("prjpcb_file", help="The path to the PrjPcb file in the source repo.")
parser.add_argument(
"pcb_file",
help="The path to the PCB file in the source repo.",
"--columns",
help=(
"A path to a JSON file mapping columns to the attributes they are from. See the README "
"for more details. Defaults to 'columns.json'."
),
default="columns.json",
)
parser.add_argument(
"--source_ref",
help="The git reference the netlist should be generated for (eg. branch name, tag name, commit SHA). Defaults to main.",
help=(
"The git reference the BOM should be generated for (eg. branch name, tag name, commit "
"SHA). Defaults to the main branch."
),
default="main",
)
parser.add_argument(
"--allspice_hub_url",
help="The URL of your AllSpice Hub instance. Defaults to https://hub.allspice.io.",
)
parser.add_argument(
"--attributes_mapping",
help="JSON text containing the attributes mapping.",
required=True,
)
parser.add_argument(
"--output_file",
help="The path to the output file. If absent, the CSV will be output to the command line.",
)
parser.add_argument(
"--group_by",
help=(
"A comma-separated list of columns to group the BOM by. If not present, the BOM will "
"be flat."
),
)
parser.add_argument(
"--variant",
help=(
"The variant of the project to generate the BOM for. If not present, the BOM will be "
"generated for the default variant."
),
default="",
)
args = parser.parse_args()
attributes_mapper = AttributesMapping.from_dict(json.loads(args.attributes_mapping))
columns_file = args.columns
with open(columns_file, "r") as f:
columns = json.loads(f.read())
# Use Environment Variables to store your auth token. This keeps your token
# secure when sharing code.
@ -60,14 +80,12 @@ if __name__ == "__main__":
if args.allspice_hub_url is None:
allspice = AllSpice(token_text=auth_token)
else:
allspice = AllSpice(
token_text=auth_token, allspice_hub_url=args.allspice_hub_url
)
allspice = AllSpice(token_text=auth_token, allspice_hub_url=args.allspice_hub_url)
repo_owner, repo_name = args.repository.split("/")
repository = allspice.get_repository(repo_owner, repo_name)
prjpcb_file = args.prjpcb_file
pcb_file = args.pcb_file
group_by = args.group_by.split(",") if args.group_by else None
print("Generating BOM...", file=sys.stderr)
@ -75,38 +93,21 @@ if __name__ == "__main__":
allspice,
repository,
prjpcb_file,
pcb_file,
attributes_mapper,
args.source_ref,
columns,
group_by=group_by,
ref=args.source_ref,
variant=args.variant if args.variant else None,
)
bom_rows = [
[
bom_row.manufacturer,
bom_row.part_number,
bom_row.quantity,
", ".join(bom_row.designators),
bom_row.description,
]
for bom_row in bom_rows
]
with ExitStack() as stack:
keys = bom_rows[0].keys()
if args.output_file is not None:
f = stack.enter_context(open(args.output_file, "w"))
writer = csv.writer(f)
writer = csv.DictWriter(f, fieldnames=keys)
else:
writer = csv.writer(sys.stdout)
writer = csv.DictWriter(sys.stdout, fieldnames=keys)
header = [
"Manufacturer",
"MFG Part Number",
"Quantity",
"Reference Designator",
"Description",
]
writer.writerow(header)
writer.writeheader()
writer.writerows(bom_rows)
print("Generated bom.", file=sys.stderr)