Jak naprawić błąd z cyklicznymi importami?

0

Ma ktoś pomysł jak pozbyć się tego błędu?

Błąd

============================= test session starts ==============================
platform linux -- Python 3.11.3, pytest-7.3.1, pluggy-1.0.0
rootdir: /home/lester29/PycharmProjects/example_shop
collected 12 items / 1 error

==================================== ERRORS ====================================
__________________ ERROR collecting tests/shop/test_orders.py __________________
ImportError while importing test module '/home/lester29/PycharmProjects/example_shop/tests/shop/test_orders.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/shop/test_orders.py:2: in <module>
    from example_shop.shop.orders import ShoppingCart, PaymentMethod, Product
example_shop/shop/orders.py:1: in <module>
    from .users import User, Address
example_shop/shop/users.py:3: in <module>
    from .products import Product
example_shop/shop/products.py:2: in <module>
    from .users import User
E   ImportError: cannot import name 'User' from partially initialized module 'example_shop.shop.users' (most likely due to a circular import) (/home/lester29/PycharmProjects/example_shop/example_shop/shop/users.py)
=========================== short test summary info ============================
ERROR tests/shop/test_orders.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 0.05s ===============================

products.py

from .money import Money
from .users import User
from decimal import Decimal
from typing import Optional


class Discount:
    def __init__(self, code: str, percent: Decimal):
        self.code = code
        self.percent = Decimal(percent)

    @property
    def percent(self) -> Decimal:
        return self._percent

    @percent.setter
    def percent(self, value: Decimal) -> None:
        if isinstance(value, float):
            raise TypeError("percent should not be float")
        if not 0 < value < 100:
            raise ValueError("percent must be a number in range (0; 100)")
        self._percent = Decimal(value)

    def __str__(self) -> str:
        return f"{self.percent}% discount, code: '{self.code}'"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(code={self.code!r}, percent={self.percent!r})"


class Product:
    def __init__(self, name: str, price: Money, seller: User, discount: Optional[Discount] = None):
        self.name = name
        self.price = price
        self.seller = seller
        self.discount = discount

    @property
    def discount_price(self) -> Money:
        return Money(amount=self.price.amount - self.discount.percent / 100 * self.price.amount,
                     currency=self.price.currency,
                     precision=self.price.precision)

    def __str__(self) -> str:
        return f"Product '{self.name}'"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(name={self.name!r}, price={self.price!r}, " \
               f"seller={self.seller!r}, discount={self.discount!r})"


users.py

import enum
from typing import NamedTuple, Optional
from .products import Product
from .payments import PaymentMethod


class Address(NamedTuple):
    street: str
    apartment_number: str
    postal_code: str
    city: str
    country: str

    def __str__(self) -> str:
        result = f"{self.street}"
        if self.apartment_number:
            result += f"/{self.apartment_number}"
        result += f", {self.postal_code} {self.city}, {self.country}"
        return result


@enum.unique
class UserType(enum.IntEnum):
    ADMINISTRATOR = enum.auto()
    REGULAR_USER = enum.auto()
    COMPANY = enum.auto()


class User:
    def __init__(self, nick: str, firstname: str, lastname: str,
                 user_address: Address, shipping_address: Address,
                 user_type: UserType = UserType.REGULAR_USER,
                 payment_method: Optional[PaymentMethod] = None,
                 sold_products: Optional[list[Product]] = None,
                 bought_products: Optional[list[Product]] = None):
        """
        Create user object (can be regular person or company).
        :param nick: a user nickname, required
        :param firstname: a user first name, required
        :param lastname: a user last name, required
        :param user_address: a user address, required
        :param shipping_address: a user shipping address, required
        :param user_type: a user type, default is regular user
        :param payment_method: default payment method
        :param sold_products: sold products, default value is empty list
        :param bought_products: bought products, default value is empty list
        """
        if not nick:
            raise ValueError("nick expected to be a non-empty string")
        self.nick = nick

        if not firstname:
            raise ValueError("expected first name to be a non-empty string")
        self.firstname = firstname

        if not lastname:
            raise ValueError("expected last name to be a non-empty string")
        self.lastname = lastname

        if not user_address:
            raise ValueError("expected user address to be a non-empty value")
        self.user_address = user_address

        if not shipping_address:
            raise ValueError("expected shipping address to be a non-empty value")
        self.shipping_address = shipping_address

        if not user_type:
            raise ValueError("expected user type to be a non-empty value")
        self.user_type = user_type

        if payment_method == PaymentMethod.TEST_CURRENCY and not __debug__:
            raise ValueError("using PaymentMethod.TEST_CURRENCY in production environment")
        if not payment_method:
            raise ValueError("expected payment method to be a non-empty value")
        self.payment_method = payment_method

        self.sold_products = [] if sold_products is None else sold_products
        self.bought_products = [] if bought_products is None else bought_products

    def __str__(self) -> str:
        return f"{self.nick} ({self.firstname} {self.lastname})"
0

Już rozwiązałem problem, wywaliłem bought_products i sold_products z klasy User.

1

Zdefiniuj wszystkie modele w jednym pliku

1

Nie lepiej jak są osobno? Wiadomo co gdzie należy

2

Aż się prosi zrefaktorować klasę User. To tak poza tematem, ale aż oczy bolą ile ta klasa ma odpowiedzialności :P

0

Co sądzisz żeby klasę User przekształcić na data class?

1

Poprzednie usunąłem, bo nie zauważyłem, że już masz porozbijane na osobne klasy. Tak, generalnie User pasuje do dataclassy, bo praktycznie co robisz w swoim kodzie to reprezentujesz pewien stan.
Te walidacje imo wsadziłbym w jakiś deskryptor, bo praktycznie robisz jeden typ walidacji dla każdego pola. No i w tym kontekście len jest imo sensowniejsze, zamiast not. Ew przeładować __setattr__

0

Jak konkretnie zrobić w dataclass walidację atrybutów deskryptorem? Da się podpiąć atrybuty dataclass pod desktyptor czy muszę przeładować __setattr__?

0
lester29 napisał(a):

Jak konkretnie zrobić w dataclass walidację atrybutów deskryptorem? Da się podpiąć atrybuty dataclass pod desktyptor czy muszę przeładować __setattr__?

Nie ma po co z tego robić dataclass. Jest dobrze tak jak jest.

1 użytkowników online, w tym zalogowanych: 0, gości: 1