chewy-attachment 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. chewy_attachment/__init__.py +3 -0
  2. chewy_attachment/core/__init__.py +21 -0
  3. chewy_attachment/core/exceptions.py +38 -0
  4. chewy_attachment/core/permissions.py +136 -0
  5. chewy_attachment/core/schemas.py +61 -0
  6. chewy_attachment/core/storage.py +158 -0
  7. chewy_attachment/core/utils.py +56 -0
  8. chewy_attachment/django_app/__init__.py +3 -0
  9. chewy_attachment/django_app/admin.py +16 -0
  10. chewy_attachment/django_app/apps.py +11 -0
  11. chewy_attachment/django_app/migrations/0001_initial.py +33 -0
  12. chewy_attachment/django_app/migrations/__init__.py +0 -0
  13. chewy_attachment/django_app/models.py +64 -0
  14. chewy_attachment/django_app/permissions.py +36 -0
  15. chewy_attachment/django_app/serializers.py +35 -0
  16. chewy_attachment/django_app/tests/__init__.py +0 -0
  17. chewy_attachment/django_app/tests/conftest.py +56 -0
  18. chewy_attachment/django_app/tests/test_views.py +207 -0
  19. chewy_attachment/django_app/urls.py +14 -0
  20. chewy_attachment/django_app/views.py +202 -0
  21. chewy_attachment/fastapi_app/__init__.py +5 -0
  22. chewy_attachment/fastapi_app/crud.py +93 -0
  23. chewy_attachment/fastapi_app/dependencies.py +190 -0
  24. chewy_attachment/fastapi_app/models.py +51 -0
  25. chewy_attachment/fastapi_app/router.py +134 -0
  26. chewy_attachment/fastapi_app/schemas.py +31 -0
  27. chewy_attachment/fastapi_app/tests/__init__.py +0 -0
  28. chewy_attachment/fastapi_app/tests/conftest.py +113 -0
  29. chewy_attachment/fastapi_app/tests/test_router.py +182 -0
  30. chewy_attachment-0.1.0.dist-info/METADATA +384 -0
  31. chewy_attachment-0.1.0.dist-info/RECORD +33 -0
  32. chewy_attachment-0.1.0.dist-info/WHEEL +4 -0
  33. chewy_attachment-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,384 @@
1
+ Metadata-Version: 2.4
2
+ Name: chewy-attachment
3
+ Version: 0.1.0
4
+ Summary: 通用图片/附件管理插件,支持 Django 和 FastAPI
5
+ Project-URL: Homepage, https://github.com/cone387/ChewyAttachment
6
+ Project-URL: Repository, https://github.com/cone387/ChewyAttachment
7
+ Project-URL: Issues, https://github.com/cone387/ChewyAttachment/issues
8
+ Project-URL: Documentation, https://github.com/cone387/ChewyAttachment#readme
9
+ Author-email: cone387 <cone387@gmail.com>
10
+ Maintainer-email: cone387 <cone387@gmail.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: attachment,django,fastapi,file-management,file-upload,image-upload
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: Django
16
+ Classifier: Framework :: Django :: 4.0
17
+ Classifier: Framework :: Django :: 5.0
18
+ Classifier: Framework :: FastAPI
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Internet :: WWW/HTTP
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Requires-Python: >=3.9
30
+ Requires-Dist: python-magic>=0.4.27
31
+ Provides-Extra: dev
32
+ Requires-Dist: django>=5.0.0; extra == 'dev'
33
+ Requires-Dist: djangorestframework>=3.14.0; extra == 'dev'
34
+ Requires-Dist: fastapi>=0.109.0; extra == 'dev'
35
+ Requires-Dist: httpx>=0.26.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
38
+ Requires-Dist: pytest-django>=4.7.0; extra == 'dev'
39
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
40
+ Requires-Dist: python-multipart>=0.0.6; extra == 'dev'
41
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
42
+ Requires-Dist: sqlmodel>=0.0.14; extra == 'dev'
43
+ Requires-Dist: uvicorn>=0.27.0; extra == 'dev'
44
+ Provides-Extra: django
45
+ Requires-Dist: django>=5.0.0; extra == 'django'
46
+ Requires-Dist: djangorestframework>=3.14.0; extra == 'django'
47
+ Provides-Extra: fastapi
48
+ Requires-Dist: fastapi>=0.109.0; extra == 'fastapi'
49
+ Requires-Dist: python-multipart>=0.0.6; extra == 'fastapi'
50
+ Requires-Dist: sqlmodel>=0.0.14; extra == 'fastapi'
51
+ Requires-Dist: uvicorn>=0.27.0; extra == 'fastapi'
52
+ Provides-Extra: test
53
+ Requires-Dist: httpx>=0.26.0; extra == 'test'
54
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
55
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
56
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
57
+ Provides-Extra: test-django
58
+ Requires-Dist: django>=5.0.0; extra == 'test-django'
59
+ Requires-Dist: djangorestframework>=3.14.0; extra == 'test-django'
60
+ Requires-Dist: httpx>=0.26.0; extra == 'test-django'
61
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test-django'
62
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test-django'
63
+ Requires-Dist: pytest-django>=4.7.0; extra == 'test-django'
64
+ Requires-Dist: pytest>=8.0.0; extra == 'test-django'
65
+ Provides-Extra: test-fastapi
66
+ Requires-Dist: fastapi>=0.109.0; extra == 'test-fastapi'
67
+ Requires-Dist: httpx>=0.26.0; extra == 'test-fastapi'
68
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test-fastapi'
69
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'test-fastapi'
70
+ Requires-Dist: pytest>=8.0.0; extra == 'test-fastapi'
71
+ Requires-Dist: python-multipart>=0.0.6; extra == 'test-fastapi'
72
+ Requires-Dist: sqlmodel>=0.0.14; extra == 'test-fastapi'
73
+ Requires-Dist: uvicorn>=0.27.0; extra == 'test-fastapi'
74
+ Description-Content-Type: text/markdown
75
+
76
+ # ChewyAttachment
77
+
78
+ 🚀 通用文件/附件管理服务 - 支持 Django & FastAPI 双框架
79
+
80
+ ## 📖 简介
81
+
82
+ ChewyAttachment 是一个通用的文件/附件管理插件,提供开箱即用的文件上传、下载、删除功能。支持作为独立的 Django 应用或 FastAPI 可插拔模块运行,适合个人自托管场景,可被多个业务系统复用。
83
+
84
+ ## ✨ 核心特性
85
+
86
+ - 🔄 **双框架支持**: 同时支持 Django 和 FastAPI
87
+ - 📁 **完整功能**: 文件上传、下载、删除、列表查询
88
+ - 🔐 **简化权限**: 基于 owner_id 的权限模型,支持 public/private 访问级别
89
+ - 🎯 **认证解耦**: 通过外部注入 user_id 实现认证解耦
90
+ - 📝 **Markdown 友好**: 返回 Markdown 格式的文件引用链接
91
+ - 🗄️ **轻量存储**: SQLite + 本地文件系统,数据库仅存元信息
92
+ - 🔌 **即插即用**: 独立于具体业务表的通用数据模型
93
+ - 🎨 **RESTful API**: 标准化的 API 设计
94
+
95
+ ## 📦 安装
96
+
97
+ ```bash
98
+ # 使用 pip 安装
99
+ pip install chewy-attachment
100
+
101
+ # 或从源码安装
102
+ git clone https://github.com/cone387/ChewyAttachment.git
103
+ cd ChewyAttachment
104
+ pip install -e .
105
+ ```
106
+
107
+ ## 🚀 快速开始
108
+
109
+ ### Django 集成
110
+
111
+ 1. **添加到 INSTALLED_APPS**
112
+
113
+ ```python
114
+ # settings.py
115
+ INSTALLED_APPS = [
116
+ # ...
117
+ 'chewy_attachment.django_app',
118
+ ]
119
+
120
+ # 配置文件存储
121
+ MEDIA_URL = '/media/'
122
+ MEDIA_ROOT = BASE_DIR / 'media'
123
+ ```
124
+
125
+ 2. **配置 URL**
126
+
127
+ ```python
128
+ # urls.py
129
+ from django.urls import path, include
130
+ from django.conf import settings
131
+ from django.conf.urls.static import static
132
+
133
+ urlpatterns = [
134
+ # ...
135
+ path('api/attachments/', include('chewy_attachment.django_app.urls')),
136
+ ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
137
+ ```
138
+
139
+ 3. **运行迁移**
140
+
141
+ ```bash
142
+ python manage.py migrate
143
+ ```
144
+
145
+ ### FastAPI 集成
146
+
147
+ ```python
148
+ from fastapi import FastAPI, Depends
149
+ from sqlalchemy import create_engine
150
+ from sqlalchemy.orm import sessionmaker, Session
151
+ from chewy_attachment.fastapi_app.router import router as attachment_router
152
+ from chewy_attachment.fastapi_app.models import Base
153
+ from chewy_attachment.fastapi_app.dependencies import get_current_user_id
154
+
155
+ app = FastAPI()
156
+
157
+ # 数据库配置
158
+ engine = create_engine("sqlite:///./attachments.db")
159
+ SessionLocal = sessionmaker(bind=engine)
160
+ Base.metadata.create_all(bind=engine)
161
+
162
+ def get_db():
163
+ db = SessionLocal()
164
+ try:
165
+ yield db
166
+ finally:
167
+ db.close()
168
+
169
+ # 自定义用户认证
170
+ async def custom_get_user_id() -> int:
171
+ # 实现你的用户认证逻辑
172
+ return 1 # 示例
173
+
174
+ # 挂载路由
175
+ app.include_router(
176
+ attachment_router,
177
+ prefix="/api/attachments",
178
+ dependencies=[Depends(get_db)]
179
+ )
180
+
181
+ # 覆盖默认的用户认证依赖
182
+ app.dependency_overrides[get_current_user_id] = custom_get_user_id
183
+ ```
184
+
185
+ ## 📚 API 文档
186
+
187
+ ### 上传文件
188
+
189
+ ```bash
190
+ POST /api/attachments/
191
+ Content-Type: multipart/form-data
192
+
193
+ 参数:
194
+ - file: 文件对象
195
+ - is_public: boolean (可选,默认 true)
196
+ - description: string (可选)
197
+ ```
198
+
199
+ ### 获取文件列表
200
+
201
+ ```bash
202
+ GET /api/attachments/
203
+
204
+ 查询参数:
205
+ - owner_id: int (可选)
206
+ - is_public: boolean (可选)
207
+ - skip: int (可选,分页偏移)
208
+ - limit: int (可选,每页数量)
209
+ ```
210
+
211
+ ### 获取文件详情
212
+
213
+ ```bash
214
+ GET /api/attachments/{attachment_id}/
215
+ ```
216
+
217
+ ### 下载文件
218
+
219
+ ```bash
220
+ GET /api/attachments/{attachment_id}/download/
221
+ ```
222
+
223
+ ### 删除文件
224
+
225
+ ```bash
226
+ DELETE /api/attachments/{attachment_id}/
227
+ ```
228
+
229
+ ## 🔐 权限模型
230
+
231
+ - **Public 文件**: 所有人可读,仅所有者可删除
232
+ - **Private 文件**: 仅所有者可读可删除
233
+ - **Owner ID**: 通过外部认证系统注入,实现认证解耦
234
+
235
+ ## 📁 数据模型
236
+
237
+ ```python
238
+ class Attachment:
239
+ id: int
240
+ filename: str # 原始文件名
241
+ file_path: str # 物理存储路径
242
+ file_size: int # 文件大小(字节)
243
+ content_type: str # MIME 类型
244
+ owner_id: int # 所有者 ID
245
+ is_public: bool # 访问级别
246
+ description: str # 描述信息
247
+ created_at: datetime # 创建时间
248
+ updated_at: datetime # 更新时间
249
+ ```
250
+
251
+ ## 🛠️ 配置选项
252
+
253
+ ### Django 配置
254
+
255
+ ```python
256
+ # settings.py
257
+
258
+ # 文件存储路径
259
+ MEDIA_ROOT = BASE_DIR / 'media'
260
+ MEDIA_URL = '/media/'
261
+
262
+ # 最大上传大小 (可选)
263
+ DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
264
+
265
+ # 自定义权限类 (可选)
266
+ # 如果不配置,则使用默认权限类
267
+ ATTACHMENTS_PERMISSION_CLASSES = [
268
+ "chewy_attachment.django_app.permissions.IsAuthenticatedForUpload",
269
+ "chewy_attachment.django_app.permissions.IsOwnerOrPublicReadOnly",
270
+ # 或使用你的自定义权限类:
271
+ # "myapp.permissions.CustomAttachmentPermission",
272
+ ]
273
+ ```
274
+
275
+ #### 自定义权限类示例
276
+
277
+ ```python
278
+ # myapp/permissions.py
279
+ from rest_framework import permissions
280
+ from chewy_attachment.django_app.models import Attachment
281
+ from chewy_attachment.core.permissions import PermissionChecker
282
+
283
+ class CustomAttachmentPermission(permissions.BasePermission):
284
+ """
285
+ 自定义附件权限类
286
+
287
+ 示例: 管理员可以访问所有文件,普通用户只能访问自己的文件
288
+ """
289
+
290
+ def has_object_permission(self, request, view, obj: Attachment):
291
+ # 管理员拥有所有权限
292
+ if request.user and request.user.is_staff:
293
+ return True
294
+
295
+ # 使用核心权限检查器
296
+ user_context = Attachment.get_user_context(request)
297
+ file_metadata = obj.to_file_metadata()
298
+
299
+ if request.method in permissions.SAFE_METHODS:
300
+ return PermissionChecker.can_view(file_metadata, user_context)
301
+
302
+ if request.method == "DELETE":
303
+ return PermissionChecker.can_delete(file_metadata, user_context)
304
+
305
+ return False
306
+
307
+ # settings.py
308
+ ATTACHMENTS_PERMISSION_CLASSES = [
309
+ "chewy_attachment.django_app.permissions.IsAuthenticatedForUpload",
310
+ "myapp.permissions.CustomAttachmentPermission",
311
+ ]
312
+ ```
313
+
314
+ ### FastAPI 配置
315
+
316
+ ```python
317
+ # 自定义存储路径
318
+ from chewy_attachment.core.storage import FileStorage
319
+
320
+ storage = FileStorage(base_path="/custom/path/media")
321
+ ```
322
+
323
+ ## 📂 项目结构
324
+
325
+ ```
326
+ ChewyAttachment/
327
+ ├── chewy_attachment/
328
+ │ ├── core/ # 核心功能模块
329
+ │ │ ├── schemas.py # 数据模式
330
+ │ │ ├── storage.py # 文件存储
331
+ │ │ ├── permissions.py # 权限控制
332
+ │ │ └── utils.py # 工具函数
333
+ │ ├── django_app/ # Django 应用
334
+ │ │ ├── models.py # Django 模型
335
+ │ │ ├── views.py # Django 视图
336
+ │ │ ├── serializers.py # DRF 序列化器
337
+ │ │ └── urls.py # URL 配置
338
+ │ └── fastapi_app/ # FastAPI 应用
339
+ │ ├── models.py # SQLAlchemy 模型
340
+ │ ├── router.py # API 路由
341
+ │ ├── crud.py # CRUD 操作
342
+ │ └── dependencies.py # 依赖注入
343
+ ├── examples/ # 示例项目
344
+ │ ├── django_example/ # Django 示例
345
+ │ └── fastapi_example/ # FastAPI 示例
346
+ └── pyproject.toml # 项目配置
347
+ ```
348
+
349
+ ## 🧪 运行测试
350
+
351
+ ```bash
352
+ # 安装测试依赖
353
+ pip install pytest pytest-django pytest-asyncio
354
+
355
+ # 运行 Django 测试
356
+ pytest chewy_attachment/django_app/tests/
357
+
358
+ # 运行 FastAPI 测试
359
+ pytest chewy_attachment/fastapi_app/tests/
360
+ ```
361
+
362
+ ## 📝 示例代码
363
+
364
+ 查看 `examples/` 目录获取完整的示例项目:
365
+
366
+ - [Django 示例](examples/django_example/)
367
+ - [FastAPI 示例](examples/fastapi_example/)
368
+
369
+ ## 🤝 贡献
370
+
371
+ 欢迎提交 Issue 和 Pull Request!
372
+
373
+ ## 📄 License
374
+
375
+ MIT License
376
+
377
+ ## 👤 作者
378
+
379
+ - GitHub: [@cone387](https://github.com/cone387)
380
+
381
+ ## 🔗 相关链接
382
+
383
+ - [项目主页](https://github.com/cone387/ChewyAttachment)
384
+ - [问题反馈](https://github.com/cone387/ChewyAttachment/issues)
@@ -0,0 +1,33 @@
1
+ chewy_attachment/__init__.py,sha256=7L5DeDKEGk3E1MimJPwOc1229oiD96jgxxw7SYNEF3I,79
2
+ chewy_attachment/core/__init__.py,sha256=U65Ugixq32koHoVTKt4qPY7Gknwz7aOGapea8KqJRWI,503
3
+ chewy_attachment/core/exceptions.py,sha256=c3gZvle7HVrm70lJaXcgiFVnwwrDyB8yfQNcPdL-LGM,1040
4
+ chewy_attachment/core/permissions.py,sha256=7Us-SXMrx4NC8pqTUTJVATGQnghG38i8tFvWlq3x-qY,3625
5
+ chewy_attachment/core/schemas.py,sha256=bwE5IpX-AFTQVTSYpYP6sGtOChJvSy3tlV5Tt08HRz8,1531
6
+ chewy_attachment/core/storage.py,sha256=GYl0n1oKCVFJwId7BlLI8eXCahTPSxjyF3YROXx-uJA,4764
7
+ chewy_attachment/core/utils.py,sha256=HtyIigHlth_XcPZc9jCQd0OaXB3nsdAn5fYXks-_RFw,1339
8
+ chewy_attachment/django_app/__init__.py,sha256=f-RS9E-YhPg-FRYmy8lFpEehdo-8Yper1tf5vViQQtA,126
9
+ chewy_attachment/django_app/admin.py,sha256=81tucyumBrXLbnFm9pLFQlDKZ9SI0Z2AY5zURnay93g,565
10
+ chewy_attachment/django_app/apps.py,sha256=U35mYn0d2oIo4Bg59eMlIsMwsOkppqPemMagYes_-ug,300
11
+ chewy_attachment/django_app/models.py,sha256=3xu4gv5pTkxf-diIHQBO5ibE9wRjMqKJ2jf3uGFHP6w,2134
12
+ chewy_attachment/django_app/permissions.py,sha256=fkBIbDvZBeLLhVdCtY_UG-uofc7R6cV9_e9CPFGxs9w,1100
13
+ chewy_attachment/django_app/serializers.py,sha256=fLeKYnOH0z2W1Cx-BH7qAzzKuHbPj7IBsoXFaqBsUMU,908
14
+ chewy_attachment/django_app/urls.py,sha256=bBSc17KZRBg-AYB79oscGSbKl_COa8I9mrK2SMc9PQE,331
15
+ chewy_attachment/django_app/views.py,sha256=IuntK-Gzui0T0gZPdqre3wHk839XFboCbw1aMBmCrno,6856
16
+ chewy_attachment/django_app/migrations/0001_initial.py,sha256=kffjcDwKW8NTCiPmfIam8kYdxnWFdT_6SThBQ3gctm4,1196
17
+ chewy_attachment/django_app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ chewy_attachment/django_app/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ chewy_attachment/django_app/tests/conftest.py,sha256=7oNZipszSvcjQWbNV1dX7f8EpH_wiF5t4uzmV5Lehrs,1489
20
+ chewy_attachment/django_app/tests/test_views.py,sha256=5oZPJAtDTjpFhZcF0NcEuBPVFtBeuLWeS-5VfLh9zHA,7605
21
+ chewy_attachment/fastapi_app/__init__.py,sha256=ZoqZPpuJXmm2yu_pEluX3_7pWGyCQg4-B06JW_d2U7Y,98
22
+ chewy_attachment/fastapi_app/crud.py,sha256=vU63MIv0YSuPz3s2wwWFZposy2uvq1TvmbDMy9otfeo,2183
23
+ chewy_attachment/fastapi_app/dependencies.py,sha256=0iPrqtza71mhvADXC6XZk4wQEtOUkLxHKTQfUBawh8A,4752
24
+ chewy_attachment/fastapi_app/models.py,sha256=hqkUEUeELFR6nL1PBLp66An9OLKKMgUWz8TZfSMW7r4,1440
25
+ chewy_attachment/fastapi_app/router.py,sha256=HTvwH5OPoGf-MsDTOcth5tUVxLCYTgsII63l_CBFrjg,3728
26
+ chewy_attachment/fastapi_app/schemas.py,sha256=9cyDtqobYsQ6oP3xkFTmLbqL--V_d_5m6Ao2UXezQG0,589
27
+ chewy_attachment/fastapi_app/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ chewy_attachment/fastapi_app/tests/conftest.py,sha256=QywBR0TpruoyVK6m1phx-jndn1nUEjfbnljHoWSl5LI,2572
29
+ chewy_attachment/fastapi_app/tests/test_router.py,sha256=8rydAXBCaYny9bKY9FH1CqsBQsteACVDPB-1JKPlWfk,6739
30
+ chewy_attachment-0.1.0.dist-info/METADATA,sha256=yATQm-gccUQw4Kw2O6LfYbKgujAmBzoRu9T36HPSCA4,11416
31
+ chewy_attachment-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
+ chewy_attachment-0.1.0.dist-info/licenses/LICENSE,sha256=TMqmDHaLuyiLHBCVMyV-tCAQlzdhhmTXRB-lP-KLGtk,1064
33
+ chewy_attachment-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cone387
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.