Repository: doccano/doccano

Issue:
Issue with pydantic in local installation
<!-- Before submitting an issue, make sure to check the docs and closed issues and FAQ to see if any of the solutions work for you. https://github.com/doccano/doccano/wiki/Frequently-Asked-Questions -->

How to reproduce the problem
---------
<!-- Include the details of how the problem occurred. Which option did you choose to install doccano? Did you come across an error? What else did you try? -->

Following the steps for a local installation in the ["Install with pip" section](https://doccano.github.io/doccano/install_and_upgrade_doccano/#install-doccano) in a virtualenv, when running `doccano task` I get the error 

```bash
pydantic.errors.PydanticImportError: `pydantic:ConstrainedStr` has been removed in V2
```

<details>
<summary>Click here for full error trace </summary>
 <br>

```bash
(.doccanovenv) ➜  project: doccano task
/user/project/.doccanovenv/lib/python3.9/site-packages/pydantic/_internal/_config.py:261: UserWarning: Valid config keys have changed in V2:
* 'schema_extra' has been renamed to 'json_schema_extra'
  warnings.warn(message, UserWarning)
[2023-07-19 12:35:54 +0000] [9403] [INFO] [django_drf_filepond.apps::ready::61] App init: no django-storages backend configured, using default (local) storage backend if set, otherwise you need to manage file storage independently of this app.
Starting task queue.
[2023-07-19 12:35:57 +0000] [9403] [ERROR] [celery.utils.dispatch.signal::send::280] Signal handler <bound method DjangoFixup.on_import_modules of <celery.fixups.django.DjangoFixup object at 0x10411df40>> raised: PydanticImportError('`pydantic:ConstrainedStr` has been removed in V2.')
Traceback (most recent call last):
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/utils/dispatch/signal.py", line 276, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/fixups/django.py", line 97, in on_import_modules
    self.worker_fixup.validate_models()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/fixups/django.py", line 137, in validate_models
    run_checks()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 494, in check
    for pattern in self.url_patterns:
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
  File "/opt/homebrew/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/config/urls.py", line 59, in <module>
    path("v1/", include("data_import.urls")),
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
  File "/opt/homebrew/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/urls.py", line 3, in <module>
    from .views import DatasetCatalog, DatasetImportAPI
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/views.py", line 7, in <module>
    from .celery_tasks import import_dataset
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/celery_tasks.py", line 11, in <module>
    from .datasets import load_dataset
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/datasets.py", line 12, in <module>
    from .pipeline.label import CategoryLabel, Label, RelationLabel, SpanLabel, TextLabel
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/pipeline/label.py", line 5, in <module>
    from pydantic import UUID4, BaseModel, ConstrainedStr, NonNegativeInt, root_validator
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/pydantic/__init__.py", line 207, in __getattr__
    return _getattr_migration(attr_name)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/pydantic/_migration.py", line 294, in wrapper
    raise PydanticImportError(f'`{import_path}` has been removed in V2.')
pydantic.errors.PydanticImportError: `pydantic:ConstrainedStr` has been removed in V2.

For further information visit https://errors.pydantic.dev/2.0.3/u/import-error
Traceback (most recent call last):
  File "/user/project/.doccanovenv/bin/doccano", line 8, in <module>
    sys.exit(main())
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/cli.py", line 177, in main
    args.handler(args)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/cli.py", line 110, in command_run_task_queue
    app.worker_main(argv=argv)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/app/base.py", line 388, in worker_main
    self.start(argv=argv)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/app/base.py", line 368, in start
    celery.main(args=argv, standalone_mode=False)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/bin/base.py", line 134, in caller
    return f(ctx, *args, **kwargs)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/bin/worker.py", line 348, in worker
    worker = app.Worker(
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/worker/worker.py", line 93, in __init__
    self.app.loader.init_worker()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/loaders/base.py", line 110, in init_worker
    self.import_default_modules()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/loaders/base.py", line 104, in import_default_modules
    raise response
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/utils/dispatch/signal.py", line 276, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/fixups/django.py", line 97, in on_import_modules
    self.worker_fixup.validate_models()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/celery/fixups/django.py", line 137, in validate_models
    run_checks()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 494, in check
    for pattern in self.url_patterns:
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
  File "/opt/homebrew/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/config/urls.py", line 59, in <module>
    path("v1/", include("data_import.urls")),
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/django/urls/conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
  File "/opt/homebrew/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/urls.py", line 3, in <module>
    from .views import DatasetCatalog, DatasetImportAPI
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/views.py", line 7, in <module>
    from .celery_tasks import import_dataset
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/celery_tasks.py", line 11, in <module>
    from .datasets import load_dataset
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/datasets.py", line 12, in <module>
    from .pipeline.label import CategoryLabel, Label, RelationLabel, SpanLabel, TextLabel
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/backend/data_import/pipeline/label.py", line 5, in <module>
    from pydantic import UUID4, BaseModel, ConstrainedStr, NonNegativeInt, root_validator
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/pydantic/__init__.py", line 207, in __getattr__
    return _getattr_migration(attr_name)
  File "/user/project/.doccanovenv/lib/python3.9/site-packages/pydantic/_migration.py", line 294, in wrapper
    raise PydanticImportError(f'`{import_path}` has been removed in V2.')
pydantic.errors.PydanticImportError: `pydantic:ConstrainedStr` has been removed in V2.
```

</details>

Fix
---------

I managed to run doccano by running `pip install pydantic==1.9` after running `pip install doccano` in the steps from the docs.

Your Environment
---------
<!-- Include details of your environment.-->
*   Operating System: MacOs 13.0.1 
*   Python Version Used: 3.9.16
*   When you install doccano: 19Jul2023 10PM UTC
*   How did you install doccano (Heroku button etc): [Install with pip with virtualenv](https://doccano.github.io/doccano/install_and_upgrade_doccano/#install-doccano) locally



Discussion:
Thanks. I can reproduce the problem.

Resolving patch:
diff --git a/backend/cli.py b/backend/cli.py
--- a/backend/cli.py
+++ b/backend/cli.py
@@ -170,7 +170,7 @@ def main():
 
     # Dispatch handler.
     args = parser.parse_args()
-    if hasattr(args, "env_file") and Path(args.env_file).is_file():
+    if hasattr(args, "env_file") and args.env_file and Path(args.env_file).is_file():
         env.read_env(args.env_file, recurse=False, override=True)
     if hasattr(args, "handler"):
         django.setup()
diff --git a/backend/data_import/pipeline/label.py b/backend/data_import/pipeline/label.py
--- a/backend/data_import/pipeline/label.py
+++ b/backend/data_import/pipeline/label.py
@@ -2,7 +2,7 @@
 import uuid
 from typing import Any, Optional
 
-from pydantic import UUID4, BaseModel, ConstrainedStr, NonNegativeInt, root_validator
+from pydantic import UUID4, BaseModel, NonNegativeInt, constr, root_validator
 
 from .label_types import LabelTypes
 from examples.models import Example
@@ -15,10 +15,6 @@
 from projects.models import Project
 
 
-class NonEmptyStr(ConstrainedStr):
-    min_length = 1
-
-
 class Label(BaseModel, abc.ABC):
     id: int = -1
     uuid: UUID4
@@ -49,14 +45,14 @@ def __hash__(self):
 
 
 class CategoryLabel(Label):
-    label: NonEmptyStr
+    label: constr(min_length=1)  # type: ignore
 
     def __lt__(self, other):
         return self.label < other.label
 
     @classmethod
     def parse(cls, example_uuid: UUID4, obj: Any):
-        return cls(example_uuid=example_uuid, label=obj)
+        return cls(example_uuid=example_uuid, label=obj)  # type: ignore
 
     def create_type(self, project: Project) -> Optional[LabelType]:
         return CategoryType(text=self.label, project=project)
@@ -66,14 +62,14 @@ def create(self, user, example: Example, types: LabelTypes, **kwargs):
 
 
 class SpanLabel(Label):
-    label: NonEmptyStr
+    label: constr(min_length=1)  # type: ignore
     start_offset: NonNegativeInt
     end_offset: NonNegativeInt
 
     def __lt__(self, other):
         return self.start_offset < other.start_offset
 
-    @root_validator
+    @root_validator(skip_on_failure=True)
     def check_start_offset_is_less_than_end_offset(cls, values):
         start_offset, end_offset = values.get("start_offset"), values.get("end_offset")
         if start_offset >= end_offset:
@@ -105,14 +101,14 @@ def create(self, user, example: Example, types: LabelTypes, **kwargs):
 
 
 class TextLabel(Label):
-    text: NonEmptyStr
+    text: constr(min_length=1)  # type: ignore
 
     def __lt__(self, other):
         return self.text < other.text
 
     @classmethod
     def parse(cls, example_uuid: UUID4, obj: Any):
-        return cls(example_uuid=example_uuid, text=obj)
+        return cls(example_uuid=example_uuid, text=obj)  # type: ignore
 
     def create_type(self, project: Project) -> Optional[LabelType]:
         return None
@@ -124,7 +120,7 @@ def create(self, user, example: Example, types: LabelTypes, **kwargs):
 class RelationLabel(Label):
     from_id: int
     to_id: int
-    type: NonEmptyStr
+    type: constr(min_length=1)  # type: ignore
 
     def __lt__(self, other):
         return self.from_id < other.from_id
diff --git a/backend/projects/models.py b/backend/projects/models.py
--- a/backend/projects/models.py
+++ b/backend/projects/models.py
@@ -1,5 +1,6 @@
 import abc
 import uuid
+from typing import Any, Dict, Optional
 
 from django.conf import settings
 from django.contrib.auth.models import User
@@ -66,7 +67,7 @@ def clone(self) -> "Project":
         project._state.adding = True
         project.save()
 
-        def bulk_clone(queryset: models.QuerySet, field_initializers: dict = None):
+        def bulk_clone(queryset: models.QuerySet, field_initializers: Optional[Dict[Any, Any]] = None):
             """Clone the queryset.
 
             Args:


Test patch: