Backend DB : ‘str’ object has no attribute ‘_sa_instance_state’

Hello and thanks for reading this.

I am learning out to use vu.js on my own. So my knowledge so far is nearly non existent. I tried to make sense of posts that have the same issue, but I could not find my solution. First here is my problem :
I am trying to add a snippet with its tags to a database.
There are 3 tables : tags, snippets, snippet_tags
I have a function that create a snippet and it works fine when i do not use tags.
I have a function that associate tags to snippets and it works fine on its own.
Now, when i try to combine then, it does not work so I can not create a snippet with tags.

Here is my DB schema

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>CREATE TABLE public.snippet_tags (
snippet_id integer NOT NULL,
tag_id integer NOT NULL
);
CREATE TABLE public.snippets (
id integer NOT NULL,
title character varying(255) NOT NULL,
description text NOT NULL,
content text NOT NULL,
category_id integer NOT NULL,
subcategory_id integer,
public boolean,
user_id integer NOT NULL,
created_at timestamp without time zone,
language character varying(50) NOT NULL
);
CREATE TABLE public.tags (
id integer NOT NULL,
name character varying(50) NOT NULL
);
</code>
<code>CREATE TABLE public.snippet_tags ( snippet_id integer NOT NULL, tag_id integer NOT NULL ); CREATE TABLE public.snippets ( id integer NOT NULL, title character varying(255) NOT NULL, description text NOT NULL, content text NOT NULL, category_id integer NOT NULL, subcategory_id integer, public boolean, user_id integer NOT NULL, created_at timestamp without time zone, language character varying(50) NOT NULL ); CREATE TABLE public.tags ( id integer NOT NULL, name character varying(50) NOT NULL ); </code>
CREATE TABLE public.snippet_tags (
    snippet_id integer NOT NULL,
    tag_id integer NOT NULL
);


CREATE TABLE public.snippets (
    id integer NOT NULL,
    title character varying(255) NOT NULL,
    description text NOT NULL,
    content text NOT NULL,
    category_id integer NOT NULL,
    subcategory_id integer,
    public boolean,
    user_id integer NOT NULL,
    created_at timestamp without time zone,
    language character varying(50) NOT NULL
);


CREATE TABLE public.tags (
    id integer NOT NULL,
    name character varying(50) NOT NULL
);

Here is below the snippet_controller.py content :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>import logging
from sqlalchemy.exc import SQLAlchemyError
from flask import request, jsonify
from app import db
from app.models.snippet import Snippet
from app.models.tag import Tag
logger = logging.getLogger('my_logger')
def create_snippet():
data = request.get_json()
title = data.get('title')
description = data.get('description')
content = data.get('content')
category_id = data.get('category_id')
subcategory_id = data.get('subcategory_id')
public = data.get('public', False)
user_id = data.get('user_id')
language = data.get('language')
tags = data.get('tags', []) # Use tags from request body
if not all([title, description, content, category_id, user_id, language]):
logger.error("Required fields are missing")
return jsonify({"error": "All required fields must be provided"}), 400
try:
logger.debug("Creating new snippet")
new_snippet = Snippet(
title=title,
description=description,
content=content,
category_id=category_id,
subcategory_id=subcategory_id,
public=public,
user_id=user_id,
language=language
)
db.session.add(new_snippet)
db.session.commit()
logger.debug(f"New snippet created with ID: {new_snippet.id}")
# Call associate_tags function to handle tag association
response = associate_tags(new_snippet.id, tags)
if response[1] != 200:
return response
return jsonify(new_snippet.to_dict()), 201
except SQLAlchemyError as e:
db.session.rollback()
logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True)
return jsonify({"error": str(e.orig)}), 500
except Exception as e:
db.session.rollback() # Ensure rollback on any exception
logger.error(f'Unhandled Exception: {str(e)}', exc_info=True)
return jsonify({"error": str(e)}), 500
def get_snippets():
try:
snippets = Snippet.query.all()
return jsonify([snippet.to_dict() for snippet in snippets])
except SQLAlchemyError as e:
return jsonify({"error": str(e.orig)}), 500
def get_snippet(snippet_id):
try:
snippet = Snippet.query.get_or_404(snippet_id)
return jsonify(snippet.to_dict())
except SQLAlchemyError as e:
return jsonify({"error": str(e.orig)}), 500
def update_snippet(snippet_id):
data = request.get_json()
try:
snippet = Snippet.query.get_or_404(snippet_id)
snippet.title = data.get('title', snippet.title)
snippet.description = data.get('description', snippet.description)
snippet.content = data.get('content', snippet.content)
snippet.category_id = data.get('category_id', snippet.category_id)
snippet.subcategory_id = data.get('subcategory_id', snippet.subcategory_id)
snippet.public = data.get('public', snippet.public)
snippet.language = data.get('language', snippet.language)
db.session.commit()
return jsonify(snippet.to_dict()), 200
except SQLAlchemyError as e:
db.session.rollback()
return jsonify({"error": str(e.orig)}), 500
def delete_snippet(snippet_id):
try:
snippet = Snippet.query.get_or_404(snippet_id)
db.session.delete(snippet)
db.session.commit()
return '', 204
except SQLAlchemyError as e:
db.session.rollback()
return jsonify({"error": str(e.orig)}), 500
def associate_tags(snippet_id, tags):
try:
snippet = Snippet.query.get_or_404(snippet_id)
snippet.tags = [] # Clear existing tags
for tag_name in tags:
logger.debug(f"Processing tag: {tag_name}")
tag = Tag.query.filter_by(name=tag_name).first()
if not tag:
logger.debug(f"Creating new tag: {tag_name}")
tag = Tag(name=tag_name)
db.session.add(tag)
db.session.commit() # Commit new tag immediately
logger.debug(f"Appending tag: {tag_name} -> Tag instance: {tag}")
snippet.tags.append(tag)
db.session.commit()
logger.debug("Tags associated and committed to DB")
return jsonify({"message": "Tags associated successfully"}), 200
except SQLAlchemyError as e:
db.session.rollback()
logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True)
return jsonify({"error": str(e.orig)}), 500
except Exception as e:
db.session.rollback() # Ensure rollback on any exception
logger.error(f'Unhandled Exception: {str(e)}', exc_info=True)
return jsonify({"error": str(e)}), 500
def get_snippets_by_tag(tag_id):
try:
tag = Tag.query.get_or_404(tag_id)
snippets = [snippet.to_dict() for snippet in tag.snippets]
return jsonify(snippets), 200
except SQLAlchemyError as e:
return jsonify({"error": str(e.orig)}), 500
</code>
<code>import logging from sqlalchemy.exc import SQLAlchemyError from flask import request, jsonify from app import db from app.models.snippet import Snippet from app.models.tag import Tag logger = logging.getLogger('my_logger') def create_snippet(): data = request.get_json() title = data.get('title') description = data.get('description') content = data.get('content') category_id = data.get('category_id') subcategory_id = data.get('subcategory_id') public = data.get('public', False) user_id = data.get('user_id') language = data.get('language') tags = data.get('tags', []) # Use tags from request body if not all([title, description, content, category_id, user_id, language]): logger.error("Required fields are missing") return jsonify({"error": "All required fields must be provided"}), 400 try: logger.debug("Creating new snippet") new_snippet = Snippet( title=title, description=description, content=content, category_id=category_id, subcategory_id=subcategory_id, public=public, user_id=user_id, language=language ) db.session.add(new_snippet) db.session.commit() logger.debug(f"New snippet created with ID: {new_snippet.id}") # Call associate_tags function to handle tag association response = associate_tags(new_snippet.id, tags) if response[1] != 200: return response return jsonify(new_snippet.to_dict()), 201 except SQLAlchemyError as e: db.session.rollback() logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True) return jsonify({"error": str(e.orig)}), 500 except Exception as e: db.session.rollback() # Ensure rollback on any exception logger.error(f'Unhandled Exception: {str(e)}', exc_info=True) return jsonify({"error": str(e)}), 500 def get_snippets(): try: snippets = Snippet.query.all() return jsonify([snippet.to_dict() for snippet in snippets]) except SQLAlchemyError as e: return jsonify({"error": str(e.orig)}), 500 def get_snippet(snippet_id): try: snippet = Snippet.query.get_or_404(snippet_id) return jsonify(snippet.to_dict()) except SQLAlchemyError as e: return jsonify({"error": str(e.orig)}), 500 def update_snippet(snippet_id): data = request.get_json() try: snippet = Snippet.query.get_or_404(snippet_id) snippet.title = data.get('title', snippet.title) snippet.description = data.get('description', snippet.description) snippet.content = data.get('content', snippet.content) snippet.category_id = data.get('category_id', snippet.category_id) snippet.subcategory_id = data.get('subcategory_id', snippet.subcategory_id) snippet.public = data.get('public', snippet.public) snippet.language = data.get('language', snippet.language) db.session.commit() return jsonify(snippet.to_dict()), 200 except SQLAlchemyError as e: db.session.rollback() return jsonify({"error": str(e.orig)}), 500 def delete_snippet(snippet_id): try: snippet = Snippet.query.get_or_404(snippet_id) db.session.delete(snippet) db.session.commit() return '', 204 except SQLAlchemyError as e: db.session.rollback() return jsonify({"error": str(e.orig)}), 500 def associate_tags(snippet_id, tags): try: snippet = Snippet.query.get_or_404(snippet_id) snippet.tags = [] # Clear existing tags for tag_name in tags: logger.debug(f"Processing tag: {tag_name}") tag = Tag.query.filter_by(name=tag_name).first() if not tag: logger.debug(f"Creating new tag: {tag_name}") tag = Tag(name=tag_name) db.session.add(tag) db.session.commit() # Commit new tag immediately logger.debug(f"Appending tag: {tag_name} -> Tag instance: {tag}") snippet.tags.append(tag) db.session.commit() logger.debug("Tags associated and committed to DB") return jsonify({"message": "Tags associated successfully"}), 200 except SQLAlchemyError as e: db.session.rollback() logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True) return jsonify({"error": str(e.orig)}), 500 except Exception as e: db.session.rollback() # Ensure rollback on any exception logger.error(f'Unhandled Exception: {str(e)}', exc_info=True) return jsonify({"error": str(e)}), 500 def get_snippets_by_tag(tag_id): try: tag = Tag.query.get_or_404(tag_id) snippets = [snippet.to_dict() for snippet in tag.snippets] return jsonify(snippets), 200 except SQLAlchemyError as e: return jsonify({"error": str(e.orig)}), 500 </code>
import logging
from sqlalchemy.exc import SQLAlchemyError
from flask import request, jsonify
from app import db
from app.models.snippet import Snippet
from app.models.tag import Tag

logger = logging.getLogger('my_logger')

def create_snippet():
    data = request.get_json()
    title = data.get('title')
    description = data.get('description')
    content = data.get('content')
    category_id = data.get('category_id')
    subcategory_id = data.get('subcategory_id')
    public = data.get('public', False)
    user_id = data.get('user_id')
    language = data.get('language')
    tags = data.get('tags', [])  # Use tags from request body

    if not all([title, description, content, category_id, user_id, language]):
        logger.error("Required fields are missing")
        return jsonify({"error": "All required fields must be provided"}), 400

    try:
        logger.debug("Creating new snippet")
        new_snippet = Snippet(
            title=title,
            description=description,
            content=content,
            category_id=category_id,
            subcategory_id=subcategory_id,
            public=public,
            user_id=user_id,
            language=language
        )

        db.session.add(new_snippet)
        db.session.commit()
        logger.debug(f"New snippet created with ID: {new_snippet.id}")

        # Call associate_tags function to handle tag association
        response = associate_tags(new_snippet.id, tags)
        if response[1] != 200:
            return response

        return jsonify(new_snippet.to_dict()), 201
    except SQLAlchemyError as e:
        db.session.rollback()
        logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True)
        return jsonify({"error": str(e.orig)}), 500
    except Exception as e:
        db.session.rollback()  # Ensure rollback on any exception
        logger.error(f'Unhandled Exception: {str(e)}', exc_info=True)
        return jsonify({"error": str(e)}), 500



def get_snippets():
    try:
        snippets = Snippet.query.all()
        return jsonify([snippet.to_dict() for snippet in snippets])
    except SQLAlchemyError as e:
        return jsonify({"error": str(e.orig)}), 500

def get_snippet(snippet_id):
    try:
        snippet = Snippet.query.get_or_404(snippet_id)
        return jsonify(snippet.to_dict())
    except SQLAlchemyError as e:
        return jsonify({"error": str(e.orig)}), 500

def update_snippet(snippet_id):
    data = request.get_json()
    try:
        snippet = Snippet.query.get_or_404(snippet_id)
        snippet.title = data.get('title', snippet.title)
        snippet.description = data.get('description', snippet.description)
        snippet.content = data.get('content', snippet.content)
        snippet.category_id = data.get('category_id', snippet.category_id)
        snippet.subcategory_id = data.get('subcategory_id', snippet.subcategory_id)
        snippet.public = data.get('public', snippet.public)
        snippet.language = data.get('language', snippet.language)
        db.session.commit()
        return jsonify(snippet.to_dict()), 200
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e.orig)}), 500

def delete_snippet(snippet_id):
    try:
        snippet = Snippet.query.get_or_404(snippet_id)
        db.session.delete(snippet)
        db.session.commit()
        return '', 204
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e.orig)}), 500

def associate_tags(snippet_id, tags):
    try:
        snippet = Snippet.query.get_or_404(snippet_id)
        snippet.tags = []  # Clear existing tags

        for tag_name in tags:
            logger.debug(f"Processing tag: {tag_name}")
            tag = Tag.query.filter_by(name=tag_name).first()
            if not tag:
                logger.debug(f"Creating new tag: {tag_name}")
                tag = Tag(name=tag_name)
                db.session.add(tag)
                db.session.commit()  # Commit new tag immediately

            logger.debug(f"Appending tag: {tag_name} -> Tag instance: {tag}")
            snippet.tags.append(tag)

        db.session.commit()
        logger.debug("Tags associated and committed to DB")
        return jsonify({"message": "Tags associated successfully"}), 200
    except SQLAlchemyError as e:
        db.session.rollback()
        logger.error(f'SQLAlchemyError: {str(e)}', exc_info=True)
        return jsonify({"error": str(e.orig)}), 500
    except Exception as e:
        db.session.rollback()  # Ensure rollback on any exception
        logger.error(f'Unhandled Exception: {str(e)}', exc_info=True)
        return jsonify({"error": str(e)}), 500

def get_snippets_by_tag(tag_id):
    try:
        tag = Tag.query.get_or_404(tag_id)
        snippets = [snippet.to_dict() for snippet in tag.snippets]
        return jsonify(snippets), 200
    except SQLAlchemyError as e:
        return jsonify({"error": str(e.orig)}), 500

Then in the snippet.py you will see that :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>from app import db
from sqlalchemy.orm import relationship
class Snippet(db.Model):
__tablename__ = 'snippets'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text, nullable=False)
content = db.Column(db.Text, nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
subcategory_id = db.Column(db.Integer, db.ForeignKey('subcategories.id'))
public = db.Column(db.Boolean, default=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
language = db.Column(db.String(50), nullable=False)
# Relationships
category = relationship('Category', backref=db.backref('snippets', lazy=True))
subcategory = relationship('Subcategory', backref=db.backref('snippets', lazy=True))
user = relationship('User', backref=db.backref('snippets', lazy=True))
tags = relationship('Tag', secondary='snippet_tags', back_populates='snippets')
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'description': self.description,
'content': self.content,
'category_id': self.category_id,
'subcategory_id': self.subcategory_id,
'public': self.public,
'user_id': self.user_id,
'created_at': self.created_at.isoformat() if self.created_at else None,
'language': self.language,
'tags': [tag.name for tag in self.tags] # Include tag names in the dictionary
}
class Favorite(db.Model):
__tablename__ = 'favorites'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
snippet_id = db.Column(db.Integer, db.ForeignKey('snippets.id'), primary_key=True)
# Relationships
user = relationship('User', backref=db.backref('favorites', cascade="all, delete-orphan"))
snippet = relationship('Snippet', backref=db.backref('favorites', cascade="all, delete-orphan"))
</code>
<code>from app import db from sqlalchemy.orm import relationship class Snippet(db.Model): __tablename__ = 'snippets' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255), nullable=False) description = db.Column(db.Text, nullable=False) content = db.Column(db.Text, nullable=False) category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False) subcategory_id = db.Column(db.Integer, db.ForeignKey('subcategories.id')) public = db.Column(db.Boolean, default=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) language = db.Column(db.String(50), nullable=False) # Relationships category = relationship('Category', backref=db.backref('snippets', lazy=True)) subcategory = relationship('Subcategory', backref=db.backref('snippets', lazy=True)) user = relationship('User', backref=db.backref('snippets', lazy=True)) tags = relationship('Tag', secondary='snippet_tags', back_populates='snippets') def to_dict(self): return { 'id': self.id, 'title': self.title, 'description': self.description, 'content': self.content, 'category_id': self.category_id, 'subcategory_id': self.subcategory_id, 'public': self.public, 'user_id': self.user_id, 'created_at': self.created_at.isoformat() if self.created_at else None, 'language': self.language, 'tags': [tag.name for tag in self.tags] # Include tag names in the dictionary } class Favorite(db.Model): __tablename__ = 'favorites' user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True) snippet_id = db.Column(db.Integer, db.ForeignKey('snippets.id'), primary_key=True) # Relationships user = relationship('User', backref=db.backref('favorites', cascade="all, delete-orphan")) snippet = relationship('Snippet', backref=db.backref('favorites', cascade="all, delete-orphan")) </code>
from app import db
from sqlalchemy.orm import relationship

class Snippet(db.Model):
    __tablename__ = 'snippets'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text, nullable=False)
    content = db.Column(db.Text, nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
    subcategory_id = db.Column(db.Integer, db.ForeignKey('subcategories.id'))
    public = db.Column(db.Boolean, default=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
    language = db.Column(db.String(50), nullable=False)

    # Relationships
    category = relationship('Category', backref=db.backref('snippets', lazy=True))
    subcategory = relationship('Subcategory', backref=db.backref('snippets', lazy=True))
    user = relationship('User', backref=db.backref('snippets', lazy=True))
    tags = relationship('Tag', secondary='snippet_tags', back_populates='snippets')

    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'content': self.content,
            'category_id': self.category_id,
            'subcategory_id': self.subcategory_id,
            'public': self.public,
            'user_id': self.user_id,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'language': self.language,
            'tags': [tag.name for tag in self.tags]  # Include tag names in the dictionary
        }

class Favorite(db.Model):
    __tablename__ = 'favorites'
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    snippet_id = db.Column(db.Integer, db.ForeignKey('snippets.id'), primary_key=True)

    # Relationships
    user = relationship('User', backref=db.backref('favorites', cascade="all, delete-orphan"))
    snippet = relationship('Snippet', backref=db.backref('favorites', cascade="all, delete-orphan")) 

Can you explain me my error and provide the correction?
The only error I see in docker is : : ‘str’ object has no attribute ‘_sa_instance_state’

Thanks for your help

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật