def isinst(obj: Any, ttype: Type, _msg: str=None) -> None:
def invariant(condition: Any, msg: str) -> None:
def failed(msg: str) -> NoReturn: def to_snake_case(camel_case: str) -> str:
    """Convert a camel case string to snake case. e.g. fooBar ==> foo_bar"""
    with_underscores = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_case)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', with_underscores).lower() class CodeWriter:
    def __init__(self) -> None:
        self.lines = []  # type: List[str]
        self.indent = 0

    def line(self, text: str) -> None:
        self.lines.append((' ' * self.indent) + text)

    def blank_line(self) -> None:
        self.lines.append('')

    def increase_indent(self) -> None:
        self.indent += 4

    def decrease_indent(self) -> None:
        if self.indent <= 0:
            raise Exception('indent cannot be negative')
        self.indent -= 4

    def result(self) -> str:
        return '\n'.join(self.lines) class DeletePentData(NamedTuple):
    type: str class EdgeToStoredIdData(NamedTuple):
    edge_name: str
    edge_id: int
    field: str class FieldVarietal(Enum):
    VANILLA = auto()
    CUSTOM = auto()
    CUSTOM_GEN = auto()
    READ_PENT = auto()
    CREATE_PENT = auto()
    DELETE_PENT = auto()
    CUSTOM_MUTATION = auto()
    UPDATE_PENT = auto()
    BROWSE_PENTS = auto()
    GEN_FROM_STORED_ID = auto()
    EDGE_TO_STORED_ID = auto()

    @property
    def is_gen_varietal(self) -> bool:
        return self in [
            FieldVarietal.CUSTOM_GEN,
            FieldVarietal.READ_PENT,
            FieldVarietal.CREATE_PENT,
            FieldVarietal.DELETE_PENT,
            FieldVarietal.CUSTOM_MUTATION,
            FieldVarietal.UPDATE_PENT,
            FieldVarietal.BROWSE_PENTS,
            FieldVarietal.GEN_FROM_STORED_ID,
            FieldVarietal.EDGE_TO_STORED_ID,
        ]

    @property
    def is_mutation(self) -> bool:
        return self in [
            FieldVarietal.CREATE_PENT, FieldVarietal.CUSTOM_MUTATION, FieldVarietal.UPDATE_PENT
        ]

    @property
    def is_custom_impl(self) -> bool:
        return self in [
            FieldVarietal.CUSTOM, FieldVarietal.CUSTOM_MUTATION, FieldVarietal.CUSTOM_GEN
        ] class GrappleDocument:
    def __init__(self, *, types: List[GrappleTypeDef]) -> None:
        self._types = types

    def pents(self) -> List[GrappleTypeDef]:
        return [obj_type for obj_type in self.object_types() if obj_type.is_pent]

    def pent_mutation_datas(self) -> List[GrappleTypeDef]:
        return [in_type for in_type in self.input_types() if in_type.is_pent_mutation_data]

    def pent_payloads(self) -> List[GrappleTypeDef]:
        return [obj_type for obj_type in self.object_types() if obj_type.is_pent_payload]

    def all_pentish(self) -> List[GrappleTypeDef]:
        return self.pents() + self.pent_mutation_datas() + self.pent_payloads() + self.enum_types()

    def mutation_type(self) -> GrappleTypeDef:
        for obj_type in self.object_types():
            if obj_type.name == 'Mutation':
                return obj_type
        return None

    def query_type(self) -> GrappleTypeDef:
        for obj_type in self.object_types():
            if obj_type.name == 'Query':
                return obj_type
        return None

    def object_types(self) -> List[GrappleTypeDef]:
        return [t for t in self._types if t.type_varietal == TypeVarietal.OBJECT]

    def input_types(self) -> List[GrappleTypeDef]:
        return [t for t in self._types if t.type_varietal == TypeVarietal.INPUT]

    def enum_types(self) -> List[GrappleTypeDef]:
        return [t for t in self._types if t.type_varietal == TypeVarietal.ENUM]

    def is_enum(self, name: str) -> bool:
        ttype = self.type_named(name)
        return ttype and ttype.type_varietal == TypeVarietal.ENUM

    def type_named(self, name: str) -> GrappleTypeDef:
        for ttype in self._types:
            if ttype.name == name:
                return ttype
        return None class GrappleField(GrappleFieldData):
    @property
    def is_custom_field(self) -> bool:
        return self.field_varietal == FieldVarietal.CUSTOM

    @property
    def python_name(self) -> str:
        if self.name == 'id':
            return 'obj_id'
        name = self.name
        if is_camel_case(name):
            name = to_snake_case(name)
        if self.field_varietal.is_gen_varietal:
            name = 'gen_' + name
        return name class GrappleFieldArgument(NamedTuple):
    name: str
    type_ref: GrappleTypeRef
    default_value: Any class GrappleTypeDef(NamedTuple):
    name: str
    fields: List[GrappleField]
    values: List[Any]
    type_id: int
    is_pent: bool
    is_pent_mutation_data: bool
    is_pent_payload: bool
    type_varietal: TypeVarietal class TypeRefVarietal(Enum):
    NAMED = auto()
    LIST = auto()
    NONNULL = auto() class GrappleTypeRef:
    def __init__(
        self,
        varietal: TypeRefVarietal,
        graphql_typename: str=None,
        python_typename: str=None,
        inner_type: 'GrappleTypeRef'=None
    ) -> None:
        self.__varietal = varietal
        self.__graphql_typename = graphql_typename
        self.__python_typename = python_typename
        self.__inner_type = inner_type

    @property
    def varietal(self) -> TypeRefVarietal:
        return self.__varietal

    @property
    def graphql_typename(self) -> str:
        return self.__graphql_typename

    @property
    def python_typename(self) -> str:
        return self.__python_typename

    @property
    def inner_type(self) -> 'GrappleTypeRef':
        return self.__inner_type class TypeVarietal(Enum):
    OBJECT = auto()
    INPUT = auto()
    ENUM = auto()
from typing import List, Tuple
from graphscale import check
from graphscale.utils import to_snake_case
from .code_writer import CodeWriter
from .parser import (
    DeletePentData, EdgeToStoredIdData, FieldVarietal, GrappleDocument, GrappleField,
    GrappleFieldArgument, GrappleTypeDef, TypeRefVarietal, GrappleTypeRef, TypeVarietal
)



GENERATED_PENT_HEADER = """#W0611: unused imports lint
#C0301: line too long
#W0613: unused args because of locals hack
#pylint: disable=W0611, C0301, W0613

from collections import namedtuple
from enum import Enum, auto
from typing import List, Any
from uuid import UUID

from graphscale import check
from graphscale.grapple.graphql_impl import (
    gen_create_pent_dynamic,
    gen_delete_pent_dynamic,
    gen_update_pent_dynamic,
    gen_browse_pents_dynamic,
    gen_pent_dynamic,
    typed_or_none,
)
from graphscale.pent import (
    Pent,
    PentMutationData,
    PentMutationPayload,
    create_pent,
    delete_pent,
    update_pent,
    PentContextfulObject,
)

"""


def print_autopents_file_body(document_ast: GrappleDocument) -> str:
    writer = CodeWriter()

    for enum_type in document_ast.enum_types():
        print_generated_enum(writer, enum_type)

    for pent_mutation_data in document_ast.pent_mutation_datas():
        print_generated_pent_mutation_data(writer, document_ast, pent_mutation_data)

    for payload_type in document_ast.pent_payloads():
        print_generated_pent_payload(writer, payload_type)

    return writer.result()


def print_generated_pents_file_body(document_ast: GrappleDocument) -> str:
    writer = CodeWriter()

    print_generated_root_class(writer, document_ast)

    for pent_type in document_ast.pents():
        print_generated_pent(writer, document_ast, pent_type)

    return writer.result()


def print_generated_enum(writer: CodeWriter, enum_type: GrappleTypeDef) -> None:

    check.invariant(enum_type.type_varietal == TypeVarietal.ENUM, 'must be enum')