'Classes with property referencing other classes

I'm creating a library, and one part of it requires a structured model.

Let's say we have sections, and each section can have subsections (categories). All of them fit this model:

class Section:
    sec_property = ""

class Category:
    sub_property = ""

class General:
    sec_property = ""
    sub_property = ""

Now, I want to have multiple sections, and each one should have custom categories, or even other sections, which would reference the specific one. Each category inherits from its section, so it has access to the sec_property of the parent.

This is what I have right now, but I'm facing some issues with it.

class CARS(General):
    sec_property = "car"

    CAR1 = None

class CAR1(CARS):
    sub_property = "car1"

CARS.CAR1 = CAR1

I want to be able to access CAR1 only through CARS -> CARS.CAR1. The issue right now is, that it acts as a property of the class, not a class itself, meaning the IDE doesn't recognize it properly.

Why I want the inheritance, and General class: I want to be able do anyClass.sub_property, and it should either return the empty string or the property itself. I also want to be able to do anyCategory.sec_property, and it should have the property of the parent class (hence the inheritance).

I want to avoid duplicating data and other bad practices.

The goal

# sections.py

class MOTOR(PARTS):
    sub_property = "motor"

class PARTS(General):
    sec_property = "parts"

    MOTOR = MOTOR


class CAR1(CARS):
    sub_property = "car1"

class CARS(General):
    sec_property = "car"

    CAR1 = CAR1
    PARTS = PARTS

In this example, the CAR1 is an alias to the class itself, not a property, constant or anything. The issue is, that it can't inherit from CARS in this case, which I would very much like to do.

I want to be able to access all categories (sub-sections) from the sections themselves, and be able to access the parent attributes (unless they are overridden, where a section would have another section as reference).

I also don't want to create duplicates (instead of inheriting the attribute sec_property assign it to each category), as that's not really a great practise.

# some other .py

import sections

sections.CARS.CAR1.sec_property
sections.CARS.CAR1.sub_property
sections.CARS.PARTS.MOTOR.sec_property
sections.CARS.PARTS.MOTOR.sub_property
sections.PARTS.MOTOR.sec_property
# etc etc

I also don't want to be able to access the categories (CAR1, MOTOR) directly, but I've already figured that one out - create __init__.py and include the stuff I want, and don't include the rest.

Clarifications

I only need to access these as special dataclasses, or enums, or something like that. I don't need to make instances of them ever.

I'm looking for a solution that IDEs recognize properly. My current solution (second codeblock with the = None) works without errors at runtime, but the IDE has no idea what I'm working with, therefore can't suggest anything. https://i.imgur.com/l5hsQOE.png https://i.imgur.com/KOkWbuM.png

Info on how I generate the file: First I make all the definitions with no connections (only inheritance), and at the end, I connect everything, so it links properly.

Specific file in question

https://github.com/Mahrkeenerh/apbi/blob/4d7bce2f297594e8d9a2798833c628bc9cd7c1f3/apbi/models/sections.py



Solution 1:[1]

Use type hints with forward references (string literals):

class PARTS(General):
    sec_property = "parts"

    MOTOR: "MOTOR" = None


class MOTOR(PARTS):
    sub_property = "motor"


class CARS(General):
    sec_property = "car"

    CAR1: "CAR1" = None
    PARTS: "PARTS" = None


class CAR1(CARS):
    sub_property = "car1"


PARTS.MOTOR = MOTOR

CARS.CAR1 = CAR1
CARS.PARTS = PARTS

Verifying the annotations programmatically:

assert "PARTS" in CARS.__annotations__
assert "MOTOR" in CARS.PARTS.__annotations__

Strictly speaking, the type hints should be Optional[Type["TYPE"]:

from typing import Optional, Type


class General:
    sec_property = ""
    sub_property = ""


class PARTS(General):
    sec_property = "parts"

    MOTOR: Optional[Type["MOTOR"]] = None


class MOTOR(PARTS):
    sub_property = "motor"


class CARS(General):
    sec_property = "car"

    CAR1: Optional[Type["CAR1"]] = None
    PARTS: Optional[Type["PARTS"]] = None


class CAR1(CARS):
    sub_property = "car1"


PARTS.MOTOR = MOTOR

CARS.CAR1 = CAR1
CARS.PARTS = PARTS

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1