Skip to content

Commit

Permalink
config and main changes
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Jun 27, 2023
1 parent 9c7a6b1 commit 4f16b0d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 54 deletions.
67 changes: 34 additions & 33 deletions src/ome_autogen/_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from enum import Enum
from typing import Any

from xsdata.codegen.writer import CodeWriter
from xsdata.models import config as cfg
from xsdata.utils import text
Expand All @@ -9,56 +6,60 @@

from ._generator import OmeGenerator

OME_BASE_TYPE = "ome_types2.model._base_type.OMEType"
OME_BASE_TYPE = "ome_types2.model._mixins.OMEType"

OUTPUT_PACKAGE = "ome_types2.model.ome_2016_06"
OME_FORMAT = "OME"


# critical to be able to use the format="OME"
CodeWriter.register_generator(OME_FORMAT, OmeGenerator)


class OmeNameCase(Enum):
"""Mimic the xsdata NameConvention enum, to modify snake case function.
We want adjacent capital letters to remain caps.
"""

OME_SNAKE = "omeSnakeCase"

def __call__(self, string: str, **kwargs: Any) -> str:
return camel_to_snake(string, **kwargs)

# @property
# def callback(self) -> Callable:
# """Return the actual callable of the scheme."""
# return camel_to_snake


def get_config() -> cfg.GeneratorConfig:
def get_config(
package: str = OUTPUT_PACKAGE, kw_only: bool = True, compound_fields: bool = False
) -> cfg.GeneratorConfig:
# ALLOW "type" to be used as a field name
text.stop_words.discard("type")

# use our own camel_to_snake
# Our's interprets adjacent capital letters as two words
# NameCase.SNAKE: 'PositionXUnit' -> 'position_xunit'
# camel_to_snake: 'PositionXUnit' -> 'position_x_unit'
cfg.__name_case_func__["snakeCase"] = camel_to_snake

# critical to be able to use the format="OME"
CodeWriter.register_generator(OME_FORMAT, OmeGenerator)

# add our own base type to every class
ome_extension = cfg.GeneratorExtension(
type=cfg.ExtensionType.CLASS,
class_name=".*",
import_string=OME_BASE_TYPE,
)

# our own snake case convention
ome_convention = cfg.NameConvention(OmeNameCase.OME_SNAKE, "value") # type: ignore
keep_case = cfg.NameConvention(cfg.NameCase.ORIGINAL, "type")

return cfg.GeneratorConfig(
output=cfg.GeneratorOutput(
package=OUTPUT_PACKAGE,
package=package,
# format.value lets us use our own generator
# kw_only is important, it makes required fields actually be required
format=cfg.OutputFormat(value=OME_FORMAT, kw_only=True),
format=cfg.OutputFormat(value=OME_FORMAT, kw_only=kw_only),
structure_style=cfg.StructureStyle.CLUSTERS,
docstring_style=cfg.DocstringStyle.NUMPY,
compound_fields=cfg.CompoundFields(enabled=True, default_name="choice"),
compound_fields=cfg.CompoundFields(enabled=compound_fields),
),
extensions=cfg.GeneratorExtensions([ome_extension]),
conventions=cfg.GeneratorConventions(field_name=ome_convention),
# Don't convert things like XMLAnnotation to XmlAnnotation
conventions=cfg.GeneratorConventions(class_name=keep_case),
)


# # These are the fields with compound "chices"
# OME ['Projects', 'Datasets', 'Folders', 'Experiments', 'Plates', 'Screens',
# 'Experimenters', 'ExperimenterGroups', 'Instruments', 'Images',
# 'StructuredAnnotations', 'ROIs', 'BinaryOnly']
# Pixels ['BinDataBlocks', 'TiffDataBlocks', 'MetadataOnly']
# Instrument ['GenericExcitationSource', 'LightEmittingDiode', 'Filament', 'Arc', 'Laser']
# BinaryFile ['External', 'BinData']
# StructuredAnnotations ['XMLAnnotation', 'FileAnnotation', 'ListAnnotation',
# 'LongAnnotation', 'DoubleAnnotation', 'CommentAnnotation',
# 'BooleanAnnotation', 'TimestampAnnotation', 'TagAnnotation',
# 'TermAnnotation', 'MapAnnotation']
# Union ['Label', 'Polygon', 'Polyline', 'Line', 'Ellipse', 'Point', 'Mask', 'Rectangle']
7 changes: 2 additions & 5 deletions src/ome_autogen/_generator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

from xsdata.codegen.models import Class
from xsdata.formats.dataclass.filters import Filters
from xsdata.formats.dataclass.generator import DataclassGenerator
from xsdata.models.config import GeneratorConfig
from xsdata_pydantic_basemodel.generator import PydanticBaseFilters

PRESERVED_NAMES = {"OME", "ROIRef", "XMLAnnotation", "ROI"}


class OmeGenerator(DataclassGenerator):
@classmethod
Expand All @@ -14,10 +13,8 @@ def init_filters(cls, config: GeneratorConfig) -> Filters:


class OmeFilters(PydanticBaseFilters):
def class_name(self, name: str) -> str:
return name if name in PRESERVED_NAMES else super().class_name(name)

def class_bases(self, obj: Class, class_name: str) -> list[str]:
# we don't need PydanticBaseFilters to add the Base class
# because we add it in the config.extensions
# This could go once PydanticBaseFilters is better about deduping
return Filters.class_bases(self, obj, class_name)
46 changes: 30 additions & 16 deletions src/ome_autogen/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import os
import subprocess
from pathlib import Path

from xsdata.codegen.transformer import SchemaTransformer
from xsdata.logger import logger

from ome_autogen import _util
from ome_autogen._config import OUTPUT_PACKAGE, get_config

from ._config import OUTPUT_PACKAGE, get_config

DO_LINT = os.environ.get("OME_AUTOGEN_LINT", "1") == "1"
SRC_PATH = Path(__file__).parent.parent
SCHEMA_FILE = SRC_PATH / "ome_types" / "ome-2016-06.xsd"
RUFF_IGNORE: list[str] = [
Expand All @@ -18,19 +18,20 @@
"E501", # Line too long
"S105", # Possible hardcoded password
]
logger.setLevel("DEBUG")


def convert_schema(
output_dir: Path | str = SRC_PATH,
schema_file: Path | str = SCHEMA_FILE,
line_length: int = 88,
ruff_ignore: list[str] = RUFF_IGNORE,
do_linting: bool = True,
do_formatting: bool = DO_LINT,
do_mypy: bool = False,
) -> None:
"""Convert the OME schema to a python model."""
config = get_config()
transformer = SchemaTransformer(print=False, config=config)
_print_gray(f"Processing {getattr(schema_file ,'name', schema_file)}...")
transformer.process_sources([Path(schema_file).resolve().as_uri()])

plurals = _util.get_plural_names(schema=schema_file)
Expand All @@ -42,24 +43,37 @@ def convert_schema(
# XXX: should we be adding s?
attr.name = plurals.get(attr.name, f"{attr.name}")

_print_gray("Writing Files...")
# xsdata doesn't support output path
with _util.cd(output_dir):
transformer.process_classes()

if not do_linting:
print(f"\033[92m\033[1m✓ OME python model created at {OUTPUT_PACKAGE}\033[0m")
if not do_formatting and not do_mypy:
_print_green(f"✓ OME python model created at {OUTPUT_PACKAGE}")
return

package_dir = Path(output_dir) / OUTPUT_PACKAGE.replace(".", "/")
black = ["black", str(package_dir), "-q", f"--line-length={line_length}"]
subprocess.check_call(black) # noqa S
if do_formatting:
_print_gray("Running black and ruff ...")

package_dir = Path(output_dir) / OUTPUT_PACKAGE.replace(".", "/")
black = ["black", str(package_dir), "-q", f"--line-length={line_length}"]
subprocess.check_call(black) # noqa S

ruff = ["ruff", "-q", "--fix", str(package_dir)]
ruff.extend(f"--ignore={ignore}" for ignore in ruff_ignore)
subprocess.check_call(ruff) # noqa S
ruff = ["ruff", "-q", "--fix", str(package_dir)]
ruff.extend(f"--ignore={ignore}" for ignore in ruff_ignore)
subprocess.check_call(ruff) # noqa S

mypy = ["mypy", str(package_dir), "--strict"]
subprocess.check_output(mypy) # noqa S
if do_mypy:
mypy = ["mypy", str(package_dir), "--strict"]
subprocess.check_output(mypy) # noqa S

# print a bold green checkmark
print(f"\033[92m\033[1m✓ OME python model created at {OUTPUT_PACKAGE}\033[0m")
_print_green(f"✓ OME python model created at {OUTPUT_PACKAGE}")


def _print_gray(text: str) -> None:
print(f"\033[90m\033[1m{text}\033[0m")


def _print_green(text: str) -> None:
print(f"\033[92m\033[1m{text}\033[0m")
File renamed without changes.

0 comments on commit 4f16b0d

Please sign in to comment.