Repository: conan-io/conan

Issue:
conan download "-p" deprecation
The same with `get` and `upload`, deprecate the `-p` in favor of full ref.

Deprecate the '-p' argument in the 'download' and 'copy' cli
As it is in `conan get...` and soon in `conan upload...`(#5224), it makes sense to deprecate the `-p` argument for `conan download...` and `conan copy...` too. 

----
It still appears in the `conan remove...` but with an slightly different meaning 😞 
```
parser.add_argument('-p', '--packages', nargs="*", action=Extender,
                    help="Select package to remove specifying the package ID")
```

 



Discussion:
(Just for reference) Deprecation of `-p` in `upload` command: https://github.com/conan-io/conan/pull/5224
I would like work on this issue.
I think It requires making 
```python
       try:
            pref = PackageReference.loads(args.pattern_or_reference, validate=True)
            reference = pref.ref.full_repr()
            package_id = pref.id
        except ConanException:
            reference = args.pattern_or_reference
            package_id = args.package
            if package_id:
                self._user_io.out.warn("Usage of `--package` argument is deprecated."
                                       " Use a full reference instead: "
                                       "`conan upload [...] {}:{}`".format(reference, package_id))
        else:
            if args.package:
                raise ConanException("Use a full package reference (preferred) or the `--package`"
```
into a function, what do you think?
Yes, it would make sense to factorize out that common code to be used by the different methods. Thanks!
Hello, I have noticed that `conan download` and `conan copy` can actually receive multiple arguments for the `--package` parameter, and it seems that there's no way to do the same thing with full reference at the moment. 
What should I do in that regard?
Uhmmm, good point.

there is the alternative:

```bash
$ conan download pkg/version@user/channel:package_id1
$ conan download pkg/version@user/channel:package_id2
$ ...
```

I think this would be a marginal use case, typically you want to do operations on all binaries, or just on one binary. Doing them on some binaries but not all should be pretty unusual.

cc/ @lasote for feedback about this UI


Resolving patch:
diff --git a/conans/client/command.py b/conans/client/command.py
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -79,7 +79,6 @@ def _fill_text(self, text, width, indent):
         text = textwrap.dedent(text)
         return ''.join(indent + line for line in text.splitlines(True))
 
-
 _QUERY_EXAMPLE = ("os=Windows AND (arch=x86 OR compiler=gcc)")
 _PATTERN_EXAMPLE = ("boost/*")
 _REFERENCE_EXAMPLE = ("MyPackage/1.2@user/channel")
@@ -366,7 +365,8 @@ def download(self, *args):
         parser.add_argument("reference",
                             help='pkg/version@user/channel')
         parser.add_argument("-p", "--package", nargs=1, action=Extender,
-                            help='Force install specified package ID (ignore settings/options)')
+                            help='Force install specified package ID (ignore settings/options)'
+                                 ' [DEPRECATED: use full reference instead]')
         parser.add_argument("-r", "--remote", help='look in the specified remote server',
                             action=OnceArgument)
         parser.add_argument("-re", "--recipe", help='Downloads only the recipe', default=False,
@@ -374,8 +374,25 @@ def download(self, *args):
 
         args = parser.parse_args(*args)
 
+        try:
+            pref = PackageReference.loads(args.reference, validate=True)
+        except ConanException:
+            reference = args.reference
+            packages_list = args.package
+
+            if packages_list:
+                self._user_io.out.warn("Usage of `--package` argument is deprecated."
+                                       " Use a full reference instead: "
+                                       "`conan download [...] {}:{}`".format(reference, packages_list[0]))
+        else:
+            reference = pref.ref.full_repr()
+            packages_list = [pref.id]
+            if args.package:
+                raise ConanException("Use a full package reference (preferred) or the `--package`"
+                                     " command argument, but not both.")
+
         self._warn_python2()
-        return self._conan.download(reference=args.reference, package=args.package,
+        return self._conan.download(reference=reference, packages=packages_list,
                                     remote_name=args.remote, recipe=args.recipe)
 
     def install(self, *args):
@@ -991,20 +1008,40 @@ def copy(self, *args):
         parser.add_argument("user_channel", default="",
                             help='Destination user/channel. e.g., lasote/testing')
         parser.add_argument("-p", "--package", nargs=1, action=Extender,
-                            help='copy specified package ID')
+                            help='copy specified package ID [DEPRECATED: use full reference instead]')
         parser.add_argument("--all", action='store_true', default=False,
                             help='Copy all packages from the specified package recipe')
         parser.add_argument("--force", action='store_true', default=False,
                             help='Override destination packages and the package recipe')
         args = parser.parse_args(*args)
 
-        if args.all and args.package:
-            raise ConanException("Cannot specify both --all and --package")
+        try:
+            pref = PackageReference.loads(args.reference, validate=True)
+        except ConanException:
+            reference = args.reference
+            packages_list = args.package
+
+            if packages_list:
+                self._user_io.out.warn("Usage of `--package` argument is deprecated."
+                                       " Use a full reference instead: "
+                                       "`conan copy [...] {}:{}`".format(reference, packages_list[0]))
+
+            if args.all and packages_list:
+                raise ConanException("Cannot specify both --all and --package")
+        else:
+            reference = pref.ref.full_repr()
+            packages_list = [pref.id]
+            if args.package:
+                raise ConanException("Use a full package reference (preferred) or the `--package`"
+                                     " command argument, but not both.")
+
+            if args.all:
+                raise ConanException("'--all' argument cannot be used together with full reference")
 
         self._warn_python2()
 
-        return self._conan.copy(reference=args.reference, user_channel=args.user_channel,
-                                force=args.force, packages=args.package or args.all)
+        return self._conan.copy(reference=reference, user_channel=args.user_channel,
+                                force=args.force, packages=packages_list or args.all)
 
     def user(self, *args):
         """
@@ -1224,8 +1261,6 @@ def upload(self, *args):
 
         try:
             pref = PackageReference.loads(args.pattern_or_reference, validate=True)
-            reference = pref.ref.full_repr()
-            package_id = pref.id
         except ConanException:
             reference = args.pattern_or_reference
             package_id = args.package
@@ -1234,16 +1269,18 @@ def upload(self, *args):
                 self._user_io.out.warn("Usage of `--package` argument is deprecated."
                                        " Use a full reference instead: "
                                        "`conan upload [...] {}:{}`".format(reference, package_id))
+
+            if args.query and package_id:
+                raise ConanException("'--query' argument cannot be used together with '--package'")
         else:
+            reference = pref.ref.full_repr()
+            package_id = pref.id
+
             if args.package:
                 raise ConanException("Use a full package reference (preferred) or the `--package`"
                                      " command argument, but not both.")
-
-        if args.query and package_id:
-            raise ConanException("'-q' and '-p' parameters can't be used at the same time")
-
-        cwd = os.getcwd()
-        info = None
+            if args.query:
+                raise ConanException("'--query' argument cannot be used together with full reference")
 
         if args.force and args.no_overwrite:
             raise ConanException("'--no-overwrite' argument cannot be used together with '--force'")
@@ -1266,18 +1303,20 @@ def upload(self, *args):
         else:
             policy = None
 
+        info = None
         try:
             info = self._conan.upload(pattern=reference, package=package_id,
                                       query=args.query, remote_name=args.remote,
                                       all_packages=args.all, policy=policy,
                                       confirm=args.confirm, retry=args.retry,
                                       retry_wait=args.retry_wait, integrity_check=args.check)
+
         except ConanException as exc:
             info = exc.info
             raise
         finally:
             if args.json and info:
-                self._outputer.json_output(info, args.json, cwd)
+                self._outputer.json_output(info, args.json, os.getcwd())
 
     def remote(self, *args):
         """
@@ -1487,8 +1526,6 @@ def get(self, *args):
 
         try:
             pref = PackageReference.loads(args.reference, validate=True)
-            reference = pref.ref.full_repr()
-            package_id = pref.id
         except ConanException:
             reference = args.reference
             package_id = args.package
@@ -1498,6 +1535,8 @@ def get(self, *args):
                                        " Use a full reference instead: "
                                        "`conan get [...] {}:{}`".format(reference, package_id))
         else:
+            reference = pref.ref.full_repr()
+            package_id = pref.id
             if args.package:
                 raise ConanException("Use a full package reference (preferred) or the `--package`"
                                      " command argument, but not both.")
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -421,15 +421,14 @@ def export_pkg(self, conanfile_path, name, channel, source_folder=None, build_fo
             raise
 
     @api_method
-    def download(self, reference, remote_name=None, package=None, recipe=False):
-        # FIXME: The "package" parameter name is very bad, it is a list of package_ids
-        if package and recipe:
-            raise ConanException("recipe parameter cannot be used together with package")
+    def download(self, reference, remote_name=None, packages=None, recipe=False):
+        if packages and recipe:
+            raise ConanException("recipe parameter cannot be used together with packages")
         # Install packages without settings (fixed ids or all)
         ref = ConanFileReference.loads(reference)
         if check_valid_ref(ref, allow_pattern=False):
-            if package and ref.revision is None:
-                for package_id in package:
+            if packages and ref.revision is None:
+                for package_id in packages:
                     if "#" in package_id:
                         raise ConanException("It is needed to specify the recipe revision if you "
                                              "specify a package revision")
@@ -438,7 +437,7 @@ def download(self, reference, remote_name=None, package=None, recipe=False):
             self._python_requires.enable_remotes(remotes=remotes)
             remote = remotes.get_remote(remote_name)
             recorder = ActionRecorder()
-            download(ref, package, remote, recipe, self._remote_manager,
+            download(ref, packages, remote, recipe, self._remote_manager,
                      self._cache, self._user_io.out, recorder, self._loader,
                      self._hook_manager, remotes=remotes)
         else:


Test patch:
diff --git a/conans/test/functional/command/copy_packages_test.py b/conans/test/functional/command/copy_packages_test.py
--- a/conans/test/functional/command/copy_packages_test.py
+++ b/conans/test/functional/command/copy_packages_test.py
@@ -27,8 +27,14 @@ class Pkg(ConanFile):
         packages = os.listdir(pkgdir)
         self.assertEqual(len(packages), 3)
 
-        # Copy just one
-        client.run("copy Hello0/0.1@lasote/stable pepe/stable -p %s" % packages[0])
+        # Copy just one with --package argument
+        client.run("copy Hello0/0.1@lasote/stable pepe/beta -p %s" % packages[0])
+        pkgdir = client.cache.package_layout(ConanFileReference.loads("Hello0/0.1@pepe/beta")).packages()
+        packages = os.listdir(pkgdir)
+        self.assertEqual(len(packages), 1)
+
+        # Copy just one with full reference
+        client.run("copy Hello0/0.1@lasote/stable:%s pepe/stable" % packages[0])
         pkgdir = client.cache.package_layout(ConanFileReference.loads("Hello0/0.1@pepe/stable")).packages()
         packages = os.listdir(pkgdir)
         self.assertEqual(len(packages), 1)
@@ -84,3 +90,15 @@ class Pkg(ConanFile):
             self.assertIn("my data!", data)
         else:
             self.assertIn("my data rev2!", data)
+
+    def test_full_reference_with_all_argument(self):
+        client = TestClient()
+        client.run("copy pkg/0.1@user/channel:{} other/channel --all".format("mimic"),
+                   assert_error=True)
+        self.assertIn("'--all' argument cannot be used together with full reference", client.out)
+
+    def test_copy_with_p_and_all(self):
+        client = TestClient()
+        client.run("copy pkg/0.1@user/channel other/channel -p {} --all".format("mimic"),
+                   assert_error=True)
+        self.assertIn("Cannot specify both --all and --package", client.out)
diff --git a/conans/test/functional/command/download_test.py b/conans/test/functional/command/download_test.py
--- a/conans/test/functional/command/download_test.py
+++ b/conans/test/functional/command/download_test.py
@@ -3,7 +3,7 @@
 from collections import OrderedDict
 
 from conans.model.ref import ConanFileReference
-from conans.test.utils.tools import TestClient, TestServer
+from conans.test.utils.tools import TestClient, TestServer, NO_SETTINGS_PACKAGE_ID, TurboTestClient
 from conans.util.files import load
 
 
@@ -12,41 +12,26 @@ class DownloadTest(unittest.TestCase):
     def download_recipe_test(self):
         server = TestServer()
         servers = {"default": server}
-        client = TestClient(servers=servers, users={"default": [("lasote", "mypass")]})
-
-        # Test argument --package and --recipe cannot be together
-        client.run("download eigen/3.3.4@conan/stable --recipe --package fake_id",
-                   assert_error=True)
-
-        self.assertIn("ERROR: recipe parameter cannot be used together with package", client.out)
+        client = TurboTestClient(servers=servers, users={"default": [("lasote", "mypass")]})
 
         # Test download of the recipe only
         conanfile = """from conans import ConanFile
 class Pkg(ConanFile):
     name = "pkg"
     version = "0.1"
-    exports_sources = "*"
 """
-        client.save({"conanfile.py": conanfile,
-                     "file.h": "myfile.h"})
-        client.run("create . lasote/stable")
         ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
-        self.assertTrue(os.path.exists(client.cache.package_layout(ref).conanfile()))
-        conan = client.cache.package_layout(ref).base_folder()
-        self.assertTrue(os.path.exists(os.path.join(conan, "package")))
-        client.run("upload pkg/0.1@lasote/stable --all")
-        client.run("remove pkg/0.1@lasote/stable -f")
-        self.assertFalse(os.path.exists(client.cache.package_layout(ref).export()))
+        client.create(ref, conanfile)
+        client.upload_all(ref)
+        client.remove_all()
+
         client.run("download pkg/0.1@lasote/stable --recipe")
 
         self.assertIn("Downloading conanfile.py", client.out)
-        self.assertIn("Downloading conan_sources.tgz", client.out)
         self.assertNotIn("Downloading conan_package.tgz", client.out)
         export = client.cache.package_layout(ref).export()
         self.assertTrue(os.path.exists(os.path.join(export, "conanfile.py")))
         self.assertEqual(conanfile, load(os.path.join(export, "conanfile.py")))
-        source = client.cache.package_layout(ref).export_sources()
-        self.assertTrue(os.path.exists(os.path.join(source, "file.h")))
         conan = client.cache.package_layout(ref).base_folder()
         self.assertFalse(os.path.exists(os.path.join(conan, "package")))
 
@@ -70,11 +55,8 @@ class Pkg(ConanFile):
         client.run("export . lasote/stable")
 
         ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
-        self.assertTrue(os.path.exists(client.cache.package_layout(ref).conanfile()))
-
         client.run("upload pkg/0.1@lasote/stable")
         client.run("remove pkg/0.1@lasote/stable -f")
-        self.assertFalse(os.path.exists(client.cache.package_layout(ref).export()))
 
         client.run("download pkg/0.1@lasote/stable")
         self.assertIn("Downloading conan_sources.tgz", client.out)
@@ -96,15 +78,13 @@ class Pkg(ConanFile):
         client.run("export . lasote/stable")
 
         ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
-        self.assertTrue(os.path.exists(client.cache.package_layout(ref).conanfile()))
 
         client.run("upload pkg/0.1@lasote/stable")
         client.run("remove pkg/0.1@lasote/stable -f")
-        self.assertFalse(os.path.exists(client.cache.package_layout(ref).export()))
 
         client.run("download pkg/0.1@lasote/stable")
         # Check 'No remote binary packages found' warning
-        self.assertTrue("WARN: No remote binary packages found in remote", client.out)
+        self.assertIn("WARN: No remote binary packages found in remote", client.out)
         # Check at least conanfile.py is downloaded
         self.assertTrue(os.path.exists(client.cache.package_layout(ref).conanfile()))
 
@@ -112,30 +92,25 @@ def download_reference_with_packages_test(self):
         server = TestServer()
         servers = {"default": server}
 
-        client = TestClient(servers=servers, users={"default": [("lasote", "mypass")]})
+        client = TurboTestClient(servers=servers, users={"default": [("lasote", "mypass")]})
         conanfile = """from conans import ConanFile
 class Pkg(ConanFile):
     name = "pkg"
     version = "0.1"
     settings = "os"
 """
+        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
 
-        client.save({"conanfile.py": conanfile})
-        client.run("create . lasote/stable")
+        client.create(ref, conanfile)
+        client.upload_all(ref)
+        client.remove_all()
+
+        client.run("download pkg/0.1@lasote/stable")
 
-        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
         package_layout = client.cache.package_layout(ref)
-        self.assertTrue(os.path.exists(package_layout.conanfile()))
 
         package_folder = os.path.join(package_layout.packages(),
                                       os.listdir(package_layout.packages())[0])
-
-        client.run("upload pkg/0.1@lasote/stable --all")
-        client.run("remove pkg/0.1@lasote/stable -f")
-        self.assertFalse(os.path.exists(package_layout.export()))
-
-        client.run("download pkg/0.1@lasote/stable")
-
         # Check not 'No remote binary packages found' warning
         self.assertNotIn("WARN: No remote binary packages found in remote", client.out)
         # Check at conanfile.py is downloaded
@@ -144,18 +119,15 @@ class Pkg(ConanFile):
         self.assertTrue(os.path.exists(package_folder))
 
     def test_download_wrong_id(self):
-        client = TestClient(servers={"default": TestServer()},
-                            users={"default": [("lasote", "mypass")]})
-        conanfile = """from conans import ConanFile
-class Pkg(ConanFile):
-    pass
-"""
-        client.save({"conanfile.py": conanfile})
-        client.run("export . pkg/0.1@lasote/stable")
-        client.run("upload pkg/0.1@lasote/stable")
-        client.run("remove pkg/0.1@lasote/stable -f")
+        client = TurboTestClient(servers={"default": TestServer()},
+                                 users={"default": [("lasote", "mypass")]})
 
-        client.run("download pkg/0.1@lasote/stable -p=wrong", assert_error=True)
+        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
+        client.export(ref)
+        client.upload_all(ref)
+        client.remove_all()
+
+        client.run("download pkg/0.1@lasote/stable:wrong", assert_error=True)
         self.assertIn("ERROR: Binary package not found: 'pkg/0.1@lasote/stable:wrong'",
                       client.out)
 
@@ -163,3 +135,64 @@ def test_download_pattern(self):
         client = TestClient()
         client.run("download pkg/*@user/channel", assert_error=True)
         self.assertIn("Provide a valid full reference without wildcards", client.out)
+
+    def download_full_reference_test(self):
+        server = TestServer()
+        servers = {"default": server}
+
+        client = TurboTestClient(servers=servers, users={"default": [("lasote", "mypass")]})
+
+        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
+        client.create(ref)
+        client.upload_all(ref)
+        client.remove_all()
+
+        client.run("download pkg/0.1@lasote/stable:{}".format(NO_SETTINGS_PACKAGE_ID))
+
+        package_layout = client.cache.package_layout(ref)
+        package_folder = os.path.join(package_layout.packages(),
+                                      os.listdir(package_layout.packages())[0])
+        # Check not 'No remote binary packages found' warning
+        self.assertNotIn("WARN: No remote binary packages found in remote", client.out)
+        # Check at conanfile.py is downloaded
+        self.assertTrue(os.path.exists(package_layout.conanfile()))
+        # Check package folder created
+        self.assertTrue(os.path.exists(package_folder))
+
+    def test_download_with_full_reference_and_p(self):
+        client = TestClient()
+        client.run("download pkg/0.1@user/channel:{package_id} -p {package_id}".
+                   format(package_id="dupqipa4tog2ju3pncpnrzbim1fgd09g"),
+                   assert_error=True)
+        self.assertIn("Use a full package reference (preferred) or the `--package`"
+                      " command argument, but not both.", client.out)
+
+    def test_download_with_package_and_recipe_args(self):
+        client = TestClient();
+        client.run("download eigen/3.3.4@conan/stable --recipe --package fake_id",
+                   assert_error=True)
+
+        self.assertIn("ERROR: recipe parameter cannot be used together with package", client.out)
+
+    def download_package_argument_test(self):
+        server = TestServer()
+        servers = {"default": server}
+
+        client = TurboTestClient(servers=servers, users={"default": [("lasote", "mypass")]})
+
+        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
+        client.create(ref)
+        client.upload_all(ref)
+        client.remove_all()
+
+        client.run("download pkg/0.1@lasote/stable -p {}".format(NO_SETTINGS_PACKAGE_ID))
+
+        package_layout = client.cache.package_layout(ref)
+        package_folder = os.path.join(package_layout.packages(),
+                                      os.listdir(package_layout.packages())[0])
+        # Check not 'No remote binary packages found' warning
+        self.assertNotIn("WARN: No remote binary packages found in remote", client.out)
+        # Check at conanfile.py is downloaded
+        self.assertTrue(os.path.exists(package_layout.conanfile()))
+        # Check package folder created
+        self.assertTrue(os.path.exists(package_folder))
\ No newline at end of file
diff --git a/conans/test/functional/command/upload_test.py b/conans/test/functional/command/upload_test.py
--- a/conans/test/functional/command/upload_test.py
+++ b/conans/test/functional/command/upload_test.py
@@ -700,3 +700,21 @@ def test_concurrent_upload(self):
             client2.run("upload lib/1.0@conan/testing")
             self.assertIn("Recipe is up to date, upload skipped", client2.out)
             self.assertNotIn("WARN", client2.out)
+
+    def upload_with_pref_and_query_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . user/testing")
+        client.run("upload Hello0/1.2.1@user/testing:{} -q 'os=Windows or os=Macos'".format(NO_SETTINGS_PACKAGE_ID),
+                   assert_error=True)
+
+        self.assertIn("'--query' argument cannot be used together with full reference", client.out)
+
+    def upload_with_package_id_and_query_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . user/testing")
+        client.run("upload Hello0/1.2.1@user/testing -p {} -q 'os=Windows or os=Macos'".format(NO_SETTINGS_PACKAGE_ID),
+                   assert_error=True)
+
+        self.assertIn("'--query' argument cannot be used together with '--package'", client.out)